配置就绪之后,真正的考验是“能不能稳定打出一个能被 Apple 接收的包”。这一节把 MAS 打包的实操步骤串起来:从清理旧产物、执行专用构建脚本,到理解 .app 与 .pkg 的区别、通过 Transporter 上传、处理版本号冲突,再到常见打包失败的系统排查。目标是让你在遇到报错时,能快速定位是配置问题、证书问题还是产物结构问题。
MAS 打包最常被低估的一步是清理。electron-builder 会在输出目录累积历史产物,如果上一次 DMG 构建的残留和本次 MAS 构建混在一起,可能出现签名范围错乱、产物混淆。
rm -rf dist
npm run compile:mascompile:mas 这类脚本通常是“先编译前端资源,再调用 electron-builder 并加载 MAS 配置”的组合。执行前确认它指向的是 electron-builder.mas.config.js,而不是默认的非 App Store 配置,否则打出来的仍是 dmg 路线产物。
注意 compile:mas 中的 mas 后缀只是脚本命名习惯,真正决定产物路线的是它加载的配置文件。如果项目没有专用脚本,也可以直接运行 electron-builder --config electron-builder.mas.config.js 达到同等效果。
清理命令不必每次都手写,很多团队会把它写进 prebuild 或单独的 clean 脚本,避免遗漏。关键是保证每次 MAS 构建都从干净状态开始,而不是依赖上次残留。
MAS 路径下,electron-builder 自身就能完成签名,前提是证书、描述文件、entitlements 都已就位。社区里偶有教程让你额外安装 electron-osx-sign 手动补签名,但在已用 electron-builder 管理签名的情况下,手动再签一次反而容易破坏链路。
正确的做法是让 electron-builder 在构建阶段统一处理:它读取 CSC_NAME 或 CSC_LINK 定位证书,结合 provisioningProfile 和 entitlements.mas.plist 完成签名。你只需要保证这些输入正确,不必在流程中插入额外的签名步骤。
统一由 electron-builder 签名的思想是“单一信任源”:构建工具知道该用哪张证书、哪个描述文件、哪份 entitlements,你只需保证输入正确。引入额外签名步骤只会制造重复与冲突。
如果你确实遇到 electron-builder 默认签名不满足需求的情况,优先查官方文档的 mac 配置项,而不是急着挂第三方补签脚本,多数场景都能在配置层解决。
MAS 构建会产出两类关键产物:
.app:应用本体,是签名与沙盒校验的主要对象,双击可运行、调试.pkg:安装包,是最终上传到 App Store Connect 的载体很多初学者误以为有了 .app 就完成了,实际上商店分发要求的是 .pkg。上传前务必确认 Transporter 加载的是 .pkg,而不是把 .app 直接拖进去。本地调试可以用 .app 验证运行行为,但提交审核必须走 .pkg。
本地验证 .app 时若启动崩溃,优先看是否缺动态库或签名无效,而不是怀疑业务代码。.app 能跑通,只代表应用本体可用,不代表 .pkg 上传与商店分发一定顺利。
pkg 与 app 的体量也不同,pkg 内含安装逻辑,体积通常更大,上传与审核耗时相应更长,排期时要预留余量。
打开 Transporter,选择构建出的 .pkg 进行上传。上传过程本身包含 Apple 的基础验证:架构完整性、签名有效性、是否引用了不允许的内容等。验证失败会给出明确错误码,按提示修正后重新上传即可。
与 Developer ID 路径不同,MAS 上传后不需要你本地跑公证脚本——Apple 在后台的签名与审核流程会接管这部分。你看到的“已接收构建版本”意味着产物进入 App Store Connect 的待审核队列,而不是已经完成所有安全处理。
Transporter 的校验是“上传前最后一道关”,它能在你本地就拦下明显错误,比等 Apple 后台处理完再报错高效得多。养成上传前先本地验证 .pkg 完整性的习惯。
MAS 对版本号极其严格。除了 package.json 里的 semver 版本,Apple 内部还看 CFBundleVersion(构建号)。如果新上传的构建号不高于已存在的版本,Apple 会直接拒绝,报类似“版本已存在”的错误。
每次上传前都要确保构建号递增,常见做法是在 CI 里基于提交次数或时间戳生成 CFBundleVersion。不要手动复用同一个构建号,也不要指望只改 semver 就能绕过——两个版本号维度都要单调上升。
构建号的管理最好自动化,而不是手动改。CI 里常见做法是取 git 提交次数或当前时间戳作为 CFBundleVersion,保证全局唯一且单调递增,避免人为漏改。
另外,package.json 的 semver 与 CFBundleVersion 要保持语义一致:前者给用户看,后者给 Apple 校验。两者都升,才不会出现“版本号变了却上传被拒”的困惑。
一个非常典型的坑是:明明想打 MAS 包,结果因为配置没切干净,打出了 DMG 或同时混入了 Developer ID 签名。根本原因是两套配置没有彻底分离,或者构建脚本默认加载了非 App Store 配置。
排查时先确认三点:构建命令加载的是 mas 配置、target 是 ['mas']、没有残留的 afterSign 公证钩子。只要这三条成立,产物才会是纯 MAS 路线,后续上传和审核才不会因“签名类型不对”被拒。
配置分离还有一层好处:当 MAS 审核政策变化时,你只需调整 mas 配置,不必担心牵连 Developer ID 分发链路。两条路各自演进,互不干扰。
当构建报错时,不要盲目改配置,按层次排查更高效:
多数失败都能映射到这五层之一。先看构建日志里报错发生在签名前还是签名后,再顺着对应层往下找,比全局乱改快得多。
日志里的阶段标记很关键:签名前报错多在 prepare 或依赖安装阶段,签名后报错多在 build-mac 阶段。看准报错发生在哪个阶段,再针对性排查,比全局搜索快得多。
把前面几节串起来:清理 → 用 mas 配置编译 → electron-builder 自动签名 → 得到 .pkg → Transporter 上传 → 版本号递增避免冲突 → App Store Connect 提交审核。这条链路在本地和 CI 里都能复现,区别只是证书与描述文件通过 Secrets 注入。
把这套流程固化到 CI 后,本地基本不再需要手动打 MAS 包,只需保证 mas 配置与 Secrets 就绪。这也是自动化交付最直观的收益:把易错的人肉步骤交给可重复的执行环境。
到这一步,MAS 上架的“能打出来、能传上去”已经打通。下一节我们把视角拉到更通用的持续交付:用 GitHub Actions 把 Windows、macOS、Linux 三端构建自动化。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 上传报“版本已存在” | CFBundleVersion 未递增 | 每次上传前递增构建号,保证单调上升 |
| Transporter 加载 .app 报错 | 商店分发需要 .pkg | 上传构建出的 .pkg 而非 .app |
| 打出的是 dmg 不是 mas 包 | 构建未加载 mas 配置 | 确认脚本指向 electron-builder.mas.config.js |
| 手动 electron-osx-sign 后签名异常 | 与 electron-builder 自动签名冲突 | 移除手动签名,交给 electron-builder 统一处理 |
| 旧产物干扰新构建 | dist 未清理 | 构建前 rm -rf dist |
| 签名后仍然报权限问题 | entitlements 缺少沙盒或权限不对 | 检查 entitlements.mas.plist 的 app-sandbox 与 groups |