peanut鸿蒙化适配实践:构建、分支治理与自动化部署全解析
1. 为什么要把 peanut 搬进鸿蒙生态做过 Flutter Web 部署的人应该都有这种体会本地跑flutter build web只是一条命令的事但真正把产物推到线上往往要手动完成一串脏活——切分支、清空旧文件、复制build/web目录、强制推送、再切回开发分支。这套流程重复三五次还能忍要是每周发版、多环境并行光是整理分支就能耗掉半天时间而且特别容易出错。我见过不止一次因为误删文件、推错分支导致线上页面直接挂掉的现场。peanut 这个三方库就是在解决这个问题。它把“构建 Web 产物、自动提交到部署分支、推送到远程仓库”这几件事封装成一条命令底层自动完成 git 分支操作不需要开发者手动切换上下文。传统的用法是直接服务 GitHub Pages 这类静态托管场景而这次我们要做的是把它适配到鸿蒙应用生态里让鸿蒙侧的前端团队也能用同一套极简流程把静态页面推送到自己的部署底座上。鸿蒙化适配不是简单换一个编译目标核心要处理的其实是三个层面的问题构建阶段的命令差异Flutter 在鸿蒙侧的工程结构和标准 Flutter Web 工程不完全一致peanut 内部默认执行的构建命令需要调整。Git 操作语义的一致性peanut 是一个强依赖 git 操作流的工具鸿蒙化项目里如果使用不同的远端托管平台分支命名、推送协议、认证方式都要兼容。分支治理模型的落地peanut 的分支策略默认是“生成分支即部署分支”而在鸿蒙多人协作场景下分支治理必须更精细避免“一个人推送覆盖所有人的产物”。这篇文章不会只停留在怎么装依赖、怎么跑命令而是会把适配过程中涉及的核心机制拆开讲清楚。我会按照实际操作的顺序从设计思路、参数细节、完整流程、问题排查四个维度展开。如果你正在做 Flutter Web 工程化或者计划把 Web 产物接入鸿蒙应用底座这篇文章可以直接当操作手册用。2. 整体设计与拆解一条命令背后的三块积木2.1 peanut 的运行模型构建、提交、推送三段式先看清楚 peanut 到底干了什么。它本质上是一个命令行工具在 Flutter 项目里通过dart run peanut触发内部依次执行读取项目配置包括输出目录、部署分支名、远端地址。执行 Flutter Web 构建命令生成build/web目录。在当前 git 仓库里临时创建一个独立分支把构建产物复制进去。提交并推送这个分支到远端。恢复原来的分支状态。用生活化的类比来说peanut 就像一个跑腿小哥你告诉他“把这份文件送到街角那家店”他会自己规划路线、自己打包、自己交到店员手里最后回来告诉你送完了。你不需要亲自出门、不需要知道店在几楼、也不需要纠结走哪条路。鸿蒙化适配的关键就在于这个“跑腿路线”要重新规划。默认情况下 peanut 会调用flutter build web并且把产物目录硬编码为build/web。鸿蒙侧如果使用自定义的构建脚本或者产物目录有偏移就必须告诉 peanut 新路线。2.2 第一块积木构建命令与产物目录的可配置化peanut 支持在命令行里传入-D参数指定 dart define也可以直接通过--flutter-command覆盖默认的构建命令。但真正影响鸿蒙化适配的是构建产物路径。在标准 Flutter 工程里flutter build web的产物固定输出到build/web。鸿蒙侧如果引入了类似“多端产物归一化”的构建流程产物可能被重定向到build/web_harmony或者某个共享目录。这时候 peanut 默认的产物识别逻辑就失效了需要用--web-out参数显式指定实际目录或者在 peanut 的配置脚本里提前做路径映射。实操层面我的建议是不要在命令行里拼太多参数而是使用 peanut 的脚本配置方式在tool/peanut.sh这类脚本里统一管理。原因有两个一是命令行参数太长容易出错二是团队协作时脚本文件可以进代码仓库做版本管理谁改了配置、为什么改都有迹可循。# 推荐的做法用脚本封装 peanut 调用 flutter pub global run peanut \ --web-out build/web_harmony \ --flutter-command fledge build web \ --branch harmony-pages这里--flutter-command我用了fledge build web作为示例实际鸿蒙化 Flutter 工程可能仍然用flutter build web关键是只要你改了构建方式就必须同步到这个参数上否则 peanut 推上去的还是旧产物。2.3 第二块积木git 分支操作的模板化防腐peanut 在背后做的 git 操作是适配中最容易踩坑的地方。它内部会执行类似这样的逻辑切换到部署分支默认是gh-pages。如果分支不存在基于当前分支创建一个孤儿分支orphan branch。把构建产物复制到工作区。提交推送。问题出在“如果分支不存在”这个分支创建策略。在鸿蒙场景下如果你的远端仓库已经有同名分支而且这个分支是从某个旧项目的目录结构继承来的peanut 默认的清空逻辑可能把不该删的.gitignore、CNAME、自定义域名配置全部干掉。我的方案是在接入 peanut 之前手工初始化一次部署分支的基线结构。比如先把CNAME、_headers、.nojekyll这类站点配置文件放进去并提交之后每次 peanut 推送就只增量更新build目录里的内容站点配置不会被覆盖。# 手工初始化部署分支只做一次 git checkout --orphan harmony-pages git rm -rf . echo example.com CNAME echo /*.map .gitignore echo .nojekyll git add . git commit -m chore: init harmony pages baseline git push origin harmony-pages之后 peanut 的每次推送都基于这个基线进行不会再出现“推一次就丢一次站点配置”的尴尬局面。2.4 第三块积木分支治理模型而不是简单推一次标题里提到的“极致分支治理”在鸿蒙化场景下具体体现为一个三级分支模型master/main开发主干所有功能分支从这里分出。release/web-x.y.z每次发布前从主干拉出的发布分支构建验证通过后再由 CI 或本地工具触发布署。harmony-pages专门的部署分支peanut 只往这个分支写构建产物。有些团队还想保留“每个大版本一个历史部署分支”的习惯比如harmony-pages-v1、harmony-pages-v2。这也没问题但我的建议是日常自动化推送统一走harmony-pages历史版本分支只在需要回滚时手动推送一次。如果每个版本都固定一个部署分支很容易出现“某次操作推错了分支线上白屏”的故障。peanut 本身支持通过--branch参数指定任意推送目标所以分支治理策略完全可以在流程层面控制不需要改动 peanut 源码。3. 核心细节解析与实操要点3.1 环境变量的注入与认证配置peanut 推送远端依赖 git 凭据。本地开发时通常已经配置好 SSH key但在鸿蒙侧的 CI 环境里往往是静态 token 方式。我的习惯是在 CI 配置里显式声明环境变量并确保 peanut 脚本能读取到。# 伪代码示意 CI 环境变量配置 env: GH_TOKEN: ${{ secrets.PAGES_TOKEN }} GIT_AUTHOR_NAME: deploy-bot GIT_AUTHOR_EMAIL: botexample.com GIT_COMMITTER_NAME: deploy-bot GIT_COMMITTER_EMAIL: botexample.com要注意的是peanut 底层走的是 git 命令它不一定认GH_TOKEN。在鸿蒙化实践里更稳妥的方案是在 CI 步骤里先配置 git 全局用户再把 token 写入 remote URL或者用GIT_ASKPASS机制。git config --global user.name deploy-bot git config --global user.email botexample.com git remote set-url origin https://user:${GH_TOKEN}host/org/repo.git这步操作只要做一次。之后 peanut 推送时就不需要人为干预认证也不会因为 CI 环境下没有 SSH key 而中断。3.2 baseHref 与资源路径的鸿蒙化适配鸿蒙应用的 Web 容器加载静态页面时对资源路径的解析方式和普通浏览器略有差异。如果你的 Flutter Web 页面最终是嵌入在鸿蒙 Web 组件里展示而不是独立域名访问那么 Flutter 构建时的baseHref一定要设置正确。peanut 的--flutter-command参数给了我们一个注入自定义构建参数的入口。比如构建时加上--base-href/web/这样页面内部所有资源请求都会带上/web/前缀和鸿蒙容器里约定的资源映射路径保持一致。# 带 baseHref 的 peanut 调用示例 flutter pub global run peanut \ --flutter-command flutter build web --base-href/web/ \ --web-out build/web \ --branch harmony-pages这里有个容易忽视的细节修改baseHref之后本地直接打开index.html可能会白屏因为file://协议下没有/web/这个路径。所以验证时一定先起本地静态服务判断路径是否正确。3.3 产物目录中的敏感文件过滤Flutter Web 构建产物里经常包含.map源码映射文件。调试时这玩意儿很有用但部署到线上等于把源码结构直接暴露了。peanut 默认会把整个build/web目录推上去所以必须在部署分支的.gitignore里加规则或者在推送前自动清理。我更推荐在构建后、推送前加一个清理步骤因为.gitignore只影响新增文件旧版本推上去的.map文件还留在 git 历史里。# 推送前的产物清理 find build/web -name *.map -type f -delete这个清理步骤建议写进 peanut 的封装脚本每次构建后自动执行。配合前面的部署分支基线配置整个流程就能做到“每次推送的产物都是干净、完整、可回滚的”。3.4 自定义域名与站点配置文件的保护部署分支上的CNAME文件是很多静态站点的命根子。如果你原本在用其他静态托管服务现在要切到鸿蒙 Web 容器域名解析和容器访问路径可能不完全等价。但不管怎样只要部署分支里存在CNAMEpeanut 的孤儿分支重建操作就很可能把它覆盖掉。所以实操上我会在 peanut 脚本里加一个后置检查推送完成后校验远端分支上的CNAME内容和本地基线是否一致不一致就自动修复并追加一次提交。这个检查逻辑虽然简单但真的能避免很多线上故障。4. 实操全过程一次完整的鸿蒙化适配记录4.1 环境准备与版本核对开始之前先确认本机环境Flutter SDK 版本建议 3.x 及以上鸿蒙侧 Flutter 工程一般会附带自己的 toolchain。Dart 环境peanut 是以 Dart 脚本方式运行的需要确保dart命令可用。Git 版本2.20 以上低版本对git switch等命令支持不完整。鸿蒙工程项目结构上建议按照“标准 Flutter 工程 鸿蒙壳工程”的方式组织。peanut 只需要在 Flutter 工程根目录运行它通过读取pubspec.yaml获取项目名不关心鸿蒙壳工程的目录布局。4.2 配置文件的整理与参数核对进入实际项目我先把 peanut 相关的配置整理到一个脚本里。这里有一个便于维护的做法把命令行参数抽成脚本变量这样即使后期要调整部署分支名也只需要改一处。#!/usr/bin/env bash set -euo pipefail PROJECT_DIR$(cd $(dirname $0)/.. pwd) WEB_OUT$PROJECT_DIR/build/web DEPLOY_BRANCHharmony-pages REMOTE_NAMEorigin cd $PROJECT_DIR # 1. 构建 Web 产物 flutter build web --base-href/web/ # 2. 清理源码映射文件 find $WEB_OUT -name *.map -type f -delete # 3. 调用 peanut 推送 flutter pub global run peanut \ --web-out $WEB_OUT \ --branch $DEPLOY_BRANCH \ --remote $REMOTE_NAME这组脚本在我的实际项目里跑了很久稳定可靠。set -euo pipefail这里尤其重要任何一个环节失败脚本都会立即退出不会带着半成品继续推送。4.3 一次典型推送的命令行实录下面是一次成功的推送过程关键输出已精简$ ./tool/deploy_web.sh [INFO] Generating web assets... [INFO] Building with base href: /web/ [INFO] Cleaning source maps... [INFO] peanut: Running Flutter build... [INFO] peanut: Switching to branch: harmony-pages [INFO] peanut: Copying build output... [INFO] peanut: Committing changes... [INFO] peanut: Pushing to origin/harmony-pages... [INFO] peanut: Restoring original branch: feature/harmony-support [SUCCESS] Deployment complete.整个过程大概一分半钟左右比手工操作快得多。而且因为是全自动的不存在“忘记复制某个目录”的情况。4.4 验证线上产物与分支状态推送完成后检查三件事远端分支的提交记录是否更新了。线上地址能否正常访问重点看页面资源路径是否和baseHref一致。本地分支状态是否已经切回开发分支。git checkout feature/harmony-support git log --oneline -1 git ls-remote origin harmony-pages如果远端分支的最新提交时间戳和刚才推送的时间一致说明推流成功。这时候哪怕线上页面还没刷新也可以确认部署流程本身是通的。5. 常见问题与排查技巧实录5.1 问题速查表症状可能原因解决方案peanut 报错branch not found部署分支从未初始化按本文 2.3 节手工初始化一次基线分支推送成功后线上页面 404baseHref与容器路径不匹配在 Flutter 构建命令里追加--base-href部署分支越来越臃肿历史构建产物不断叠加检查部署分支 .gitignore并定期重建基线分支CI 推送报权限错误远端认证方式与 CI 环境不匹配改用 token 写入 remote URL本地构建后手动打开 index.html 白屏baseHref导致本地 file 协议无法解析使用python3 -m http.server起本地服务源码.map文件泄漏未在推送前清理产物在封装脚本中统一删除.map5.2 分支被占用的现场还原有一段时间我所在的项目组在同一个仓库里既放鸿蒙壳工程又放 Flutter Web 工程。某个同事直接用git push origin master把代码推到主干结果部署分支harmony-pages被远端规则保护他误以为推送失败又执行了强推。最后的结果是线上页面回退到几个版本之前所有缓存全部失效。排查过程其实不复杂先看远端分支保护规则确认强推被拒绝再看 peanut 推送时用的 token 权限发现这个 token 有force_push权限。处理方式是重新生成一个低权限 token只允许非强推方式更新部署分支。5.3 缓存策略与 sync 时机鸿蒙容器加载静态页面时可能存在缓存层。如果 peanut 推送成功但页面内容一直不变先别急着怀疑 peanut先确认缓存策略。我通常在部署分支的_headers文件里显式设置缓存控制字段/* Cache-Control: no-cache这是为了让每次部署后客户端都能尽快拿到新版本。如果你的页面在鸿蒙壳里只是一个 Web 组件不需要强缓存来优化加载性能这个设置可以保留如果页面访问量大可以把Cache-Control调整为max-age3600但那样每次发版后需要手动刷新缓存。5.4 多环境并行的分支隔离技巧团队里如果同时维护测试环境和生产环境peanut 默认的“一个部署分支”模型可能会互相干扰。我的做法是引入环境后缀harmony-pages生产环境产物。harmony-pages-staging测试环境产物。在 peanut 调用脚本里通过环境变量区分DEPLOY_BRANCHharmony-pages if [ $ENV staging ]; then DEPLOY_BRANCHharmony-pages-staging fi这样两套环境互不干扰而且推送逻辑完全复用。唯一要注意的是这两个分支访问路径不同baseHref也要对应调整否则资源加载会乱。5.5 断网、中断与半成品状态恢复peanut 执行到一半断网是一个很恶心但必须面对的场景。因为它的分支切换逻辑是切到部署分支 → 复制产物 → 提交 → 推送 → 切回开发分支。如果在“复制产物”之后、提交之前断网你的当前工作区会停留在部署分支上而且带着未提交的产物文件。恢复步骤git switch feature/harmony-support git branch -D harmony-pages git checkout orphan 的替代方案更稳妥的做法是每次 peanut 推送前先在脚本里自动保存当前分支名并在脚本末尾显式恢复。如果脚本中途崩溃至少可以通过读取保存的文件知道原来在哪个分支。6. 扩展从单点部署到“一键直达”的数字化底座6.1 把 peanut 集成进鸿蒙侧的服务编排适配完成之后peanut 解决的不只是“部署”这一件事。在鸿蒙应用里前端静态资源往往和其他服务比如配置中心、离线包管理紧密配合。peanut 推送产物的动作可以作为 CI 流水线的一个环节与其他自动化任务串联。举例来说推送完成后可以自动触发一个 Webhook通知鸿蒙侧的服务编排模块“有新的页面版本可用”由编排模块决定是静默更新还是提示用户刷新。这样一来部署的终点不再是托管服务器而是整个应用运行时的状态管理系统也就是标题里说的“数字化底座”。6.2 分支治理与版本回滚的联动peanut 的推送分支可以打 tag。每次成功部署后在部署分支上打个版本 tag例如web-preview-20240601。这样出问题时只需要把分支重置到对应 tag再强推一次就能快速回滚。git tag web-$(date %Y%m%d-%H%M%S) harmony-pages git push origin --tags这套机制让“极致分支治理”不再是一个空话每个部署产物都有对应的 tag每次回滚都有据可查。尤其是多人协作的项目分支合并历史乱了以后tag 就是一个稳定的回溯锚点。6.3 团队协作时的角色隔离与权限边界最后是权限治理的层面。peanut 推送时最好使用专门的部署账号或 bot token而不是团队成员的私人 token。这样即使有人离职、token 失效也不会影响部署流水线。同时在远端仓库层面给部署分支配置“禁止强推”策略从机制上防止意外覆盖历史版本。我见过一个团队因为所有开发人员共用同一个高权限 token某次误操作把部署分支强推成空分支导致线上服务中断小半天。这种事故完全可以靠本文提到的“最小权限 分支保护 基线分支”这套组合拳避免掉。整体走完一遍之后我的体会是peanut 鸿蒙化适配本身没什么神秘技术难的是把构建、推送、分支治理这几个环节在自动化脚本里做到“可重复、可回滚、可审计”。把这三个属性都处理好了Web 部署这件事就能真正成为鸿蒙应用体系里一个低调但稳定的基石。如果你也在做类似的适配建议先从一条脚本命令开始把手工操作逐步替换成自动化再慢慢加入分支保护和权限治理每一步都能看到实实在在的效率提升。