edit | blame | history | raw

2026-06-05 Mac App Store 上传教训与预检清单

这次的问题

今天上传成功前反复踩了三个坑:

  1. CFBundleIdentifier 与 Apple Developer profile 的 Bundle ID 不一致。
  2. Info.plist 缺少 Mac App Store 必填的 LSApplicationCategoryType
  3. .app 嵌入的 provisioning profile 有 com.apple.application-identifier,但签名 entitlements 没有同一个值,触发 Transporter/TestFlight 90886
  4. 下载得到的 provisioning profile 带 com.apple.quarantine 扩展属性,复制进 .app 后被打进 .pkg,触发 Transporter/TestFlight 91109

这些都不是业务功能问题,而是 Mac App Store 上传包的元数据和签名验收不完整。

必须记住的规则

  • Transporter 不是完整的本地 QA 工具;不能把用户上传失败当成检查方式。
  • pkgutil --check-signature 只能证明 .pkg 被 Installer 证书签了,不能证明 App Store 会接受。
  • codesign -vvv --deep --strict 只能证明签名结构有效,不能证明 entitlements 与 embedded profile 一致。
  • 每次重传必须换新的 CFBundleVersion build 号。
  • 同一个 App Store 版本可以保持 CFBundleShortVersionString=2.1.28,但每个新上传包必须有新的 build。
  • 最终给用户上传的只能是 *-universal-macappstore.pkg,不能是 sandbox POC DMG,也不能是旧 build。

上传前硬性预检

在把 .pkg 交给用户上传前,必须对最终 .pkg 展开检查,而不是只检查 build 目录里的 .app

必须检查:

  • CFBundleIdentifier=com.shanghai3168.privatevoicedictation
  • CFBundleShortVersionString 与本次发布版本一致
  • CFBundleVersion 是本次新 build
  • LSApplicationCategoryType=public.app-category.productivity
  • embedded provisioning profile 的 App ID 是 CR3J54M8BQ.com.shanghai3168.privatevoicedictation
  • signed entitlements 的 com.apple.application-identifierCR3J54M8BQ.com.shanghai3168.privatevoicedictation
  • signed entitlements 的 com.apple.developer.team-identifierCR3J54M8BQ
  • signed entitlements 包含 com.apple.security.app-sandbox=true
  • app 主程序是 universal arm64 + x86_64
  • libsherpa-onnx-c-api.dylib 是 universal arm64 + x86_64
  • libonnxruntime.1.24.4.dylib 是 universal arm64 + x86_64
  • codesign -vvv --deep --strict 对最终 app PASS
  • pkgutil --check-signature 对最终 pkg PASS
  • 最终 .pkg 展开后的 .app 内没有 com.apple.quarantine 扩展属性
  • release、桌面上传目录、运营资料目录里的 pkg SHA256 完全一致

参考检查命令

PKG="/path/to/PrivateVoice-Dictation-2.1.28-buildYYYYMMDD.HHMM-universal-macappstore.pkg"
TMP="/tmp/privatevoice-pkg-check"

rm -rf "$TMP"
pkgutil --expand-full "$PKG" "$TMP"
APP="$(find "$TMP" -name 'PrivateVoice Dictation.app' -type d -print -quit)"

/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c 'Print :LSApplicationCategoryType' "$APP/Contents/Info.plist"

PROFILE_PLIST="$TMP/profile.plist"
security cms -D -i "$APP/Contents/embedded.provisionprofile" > "$PROFILE_PLIST"
/usr/libexec/PlistBuddy -c 'Print :Entitlements:com.apple.application-identifier' "$PROFILE_PLIST"
/usr/libexec/PlistBuddy -c 'Print :Entitlements:com.apple.developer.team-identifier' "$PROFILE_PLIST"

SIGNED_ENTITLEMENTS="$TMP/signed-entitlements.plist"
codesign -d --entitlements :- "$APP" > "$SIGNED_ENTITLEMENTS" 2>/dev/null
/usr/libexec/PlistBuddy -c 'Print :com.apple.application-identifier' "$SIGNED_ENTITLEMENTS"
/usr/libexec/PlistBuddy -c 'Print :com.apple.developer.team-identifier' "$SIGNED_ENTITLEMENTS"
/usr/libexec/PlistBuddy -c 'Print :com.apple.security.app-sandbox' "$SIGNED_ENTITLEMENTS"

file "$APP/Contents/MacOS/PrivateVoice Dictation"
file "$APP/Contents/Frameworks/libsherpa-onnx-c-api.dylib"
file "$APP/Contents/Frameworks/libonnxruntime.1.24.4.dylib"

codesign -vvv --deep --strict "$APP"
pkgutil --check-signature "$PKG"
xattr -lr "$APP" | grep -F 'com.apple.quarantine' && exit 1 || true
shasum -a 256 "$PKG"

今天形成的修复

  • 正式 Bundle ID 固定为 com.shanghai3168.privatevoicedictation
  • Mac App Store 分类固定为 public.app-category.productivity
  • 打包脚本必须从 provisioning profile 合并以下 entitlements 后再签名:
  • com.apple.application-identifier
  • com.apple.developer.team-identifier
  • keychain-access-groups
  • 当前可上传包是 20260605.0216,不要再上传 20260604.004920260604.2353
  • 如果遇到 91109,必须在组装 app 后、签名后、productbuild 前对 .app 执行 xattr -cr,并展开最终 .pkg 检查没有 com.apple.quarantine

上传后还要检查

  • App Store Connect 里选择最新 build。
  • 付费应用必须确认 Paid Apps Agreement、税务和银行信息已生效。
  • 价格不在 Transporter 设置;在 App Store Connect 的 Monetization / Pricing and Availability 设置。
  • 提交审核前确认截图、隐私政策、支持 URL、审核备注、隐私问卷和价格都已填完。