最近做了一件很具体的事:基于官方预编译 VSCodium,打了一个可以发给同事直接安装的 macOS 应用。名字叫 PM Pro,Bundle ID 是 com.xiezhixin.pmpro,内置 OpenAI Codex 插件,默认中文、默认浅色主题,最终产出一个 .dmg 文件,双击就能装好。

过程中的坑比想象中多,但最后都踩过去了。

目标是什么

一开始目标很简单:

  • 用官方预编译 VSCodium,不从源码编译
  • 改 App 显示名为 PM Pro
  • 修改 Bundle ID 为 com.xiezhixin.pmpro
  • 内置 Codex 插件
  • 内置默认配置(中文 + 浅色主题)
  • 打包成 DMG

做到一半才发现,macOS App 分发真正麻烦的地方不在"改名",而在 Electron helper、代码签名、公证和用户首次启动体验。

基础打包流程

基础流程是这样的:

  1. 下载官方 VSCodium macOS zip
  2. 解压得到 VSCodium.app
  3. 重命名为 PM Pro.app
  4. 修改 Contents/Info.plist
  5. 解压 VSIX 扩展到 App 内部
  6. 写入默认用户配置
  7. 生成 DMG

关键元数据:

  • App 名称:PM Pro
  • Bundle ID:com.xiezhixin.pmpro
  • VSCodium 版本:1.116.02821
  • Codex 插件:openai.chatgpt 26.5422.71525
  • 中文语言包:MS-CEINTL.vscode-language-pack-zh-hans 1.110.2026050117(选了兼容 ^1.110.0 的版本,因为最新版语言包要求 VS Code ^1.118.1,不兼容当前 VSCodium 1.116)

坑一:改完 Info.plist 后不能再调用 codium CLI

最早的脚本里用的是:

codium --install-extension xxx.vsix --extensions-dir ...

但先改了 Info.plist 之后,原 App 签名就失效了。macOS 直接把 codium CLI 杀掉:

Killed: 9

解决方式是:VSIX 本质上是一个 zip,直接读取 extension/package.json,然后把 extension/ 目录解压到目标扩展目录即可,完全不需要启动 App。

扩展目录名按 VS Code 约定生成:publisher.name-version,例如 openai.chatgpt-26.5422.71525

坑二:Electron Helper 必须一起改名

第一次能打出 DMG,但打开后马上崩溃,系统日志里有一行很关键:

Unable to find helper app

原因是 Electron macOS App 会根据主 App 名称查找 helper。主 App 改成了 PM Pro,但包里还叫:

  • VSCodium Helper.app
  • VSCodium Helper (Renderer).app
  • VSCodium Helper (GPU).app
  • VSCodium Helper (Plugin).app

所以必须同步改成 PM Pro Helper.app 等,同时还要改每个 helper 的 CFBundleIdentifier、CFBundleName、CFBundleDisplayName、CFBundleExecutable,以及 Contents/MacOS/ 下的真实可执行文件名。

默认中文和浅色主题

默认配置有两个层次。

第一层是用户配置:

{
  "workbench.colorTheme": "Light Modern"
}

第二层是语言:

{
  "locale": "zh-cn"
}

但只放 argv.json 不够,实际启动时进程仍然可能是 --lang=en-US。最后做了一个启动器:把原来的 VSCodium 主程序改名为 VSCodium-bin,再生成一个新的 PM Pro 启动脚本。

启动器做三件事:

  1. 首次启动时,把默认配置复制到 ~/Library/Application Support/PM Pro/User
  2. 使用独立用户数据目录,避免污染 VSCodium 的配置
  3. 强制带 --locale zh-cn 启动

关键启动命令:

exec "$REAL_EXECUTABLE" --locale zh-cn --user-data-dir "$USER_DATA_DIR" "$@"

DMG 安装体验

一开始生成 DMG 时只把 .app 放进去。技术上可用,但对不熟悉 macOS 的用户不友好。

后来改成标准安装盘结构:

ditto "$APPDIR" "$DMG_STAGING/${CUSTOM_APP_NAME}.app"
ln -s /Applications "$DMG_STAGING/Applications"
hdiutil create -volname "$CUSTOM_APP_NAME" -srcfolder "$DMG_STAGING" -ov -format UDZO "$DMG_OUTPUT"

这样用户打开 DMG 就知道要把 App 拖到 Applications。

体积优化:只保留当前平台的 Codex 二进制

Codex VSIX 很大,因为它包含多平台二进制:macOS arm64、macOS x64、Linux x64、Linux arm64、Windows x64、Windows arm64。我们只打 arm64 的 macOS 包,所以只保留 bin/macos-aarch64,删除其他平台后,DMG 从约 736MB 降到了约 310MB。

正式分发:Developer ID 签名和 Notarization

本地 ad-hoc 签名可以自己打开,但发给同事会出现:

Apple 无法验证"PM Pro"是否包含可能危害 Mac 安全或泄漏隐私的恶意软件。

这是 Gatekeeper 在拦截未公证 App。正式分发需要:

  1. Developer ID Application 证书
  2. Hardened Runtime
  3. Apple notarization
  4. Staple 公证票据

签名身份是 Developer ID Application: zhixin xie (77TN4427T5)。签名命令核心参数:

codesign --force --deep --options runtime --timestamp \
  --entitlements entitlements.plist \
  --sign "Developer ID Application: zhixin xie (77TN4427T5)" \
  "PM Pro.app"

DMG 也要签,然后提交公证:

xcrun notarytool submit "PM Pro-arm64.dmg" \
  --keychain-profile PMProNotary --wait

公证成功后 staple:

xcrun stapler staple "PM Pro-arm64.dmg"
xcrun stapler validate "PM Pro-arm64.dmg"

最终结果:status: Accepted,Gatekeeper 校验通过:PM Pro-arm64.dmg: accepted, source=Notarized Developer ID

坑三:插件里的裸二进制也要单独签名

第一次提交 notarization 返回 Invalid。Apple 日志指出 Codex 插件里的 rg 没有合法 Developer ID 签名:

openai.chatgpt-26.5422.71525/bin/macos-aarch64/rg
The binary is not signed with a valid Developer ID certificate.

codesign --deep 并不总是可靠覆盖扩展目录里的裸二进制。最后在签 App 之前,显式找到扩展里的 macOS 可执行文件逐个签名:

find "$APPDIR/Contents/Resources/app/extensions" \
  -type f \
  \( -path "*/bin/macos-aarch64/*" -o -path "*/bin/macos-x86_64/*" \) \
  -perm -111

然后逐个执行 codesign --force --options runtime --timestamp --entitlements entitlements.plist --sign "$SIGN_IDENTITY" "$binary"。这一步解决了 notarization 的关键问题。

最终效果

最终产物是一个可以正式分发的 macOS arm64 DMG:

PM Pro-arm64.dmg

它具备:

  • PM Pro 显示名
  • com.xiezhixin.pmpro Bundle ID
  • 内置 Codex
  • 默认中文
  • 默认浅色主题
  • 独立用户数据目录
  • 标准拖拽安装体验
  • Developer ID 签名
  • Apple 公证通过并已 staple

这次最大的收获是:做一个"能在自己机器上跑"的 macOS App 很容易,做一个"同事双击也能放心打开"的 macOS App,关键在签名、公证、helper 和默认用户体验这些细节。