appuploader-cli 使用教程:在 Windows 上用命令行把 IPA 上传到 App Store

appuploader-cli 使用教程:在 Windows 上用命令行把 IPA 上传到 App Store

一条命令就能把打好的 IPA 传到 App Store Connect,不用 Mac,也不用装 Xcode:

bash 复制代码
appuploader-cli upload -f Payload.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop

appuploader-cli 是「开心上架(AppUploader)」自带的命令行工具,装好软件它就在安装目录里。下面从零走一遍:怎么找到它、第一次上传要准备什么、团队和流水线里该换成哪种登录方式、传不上去时怎么定位问题。

什么时候需要用命令行

图形界面点两下就能提交,多数人其实用不上命令行。真正需要它的是这三种情况:

  • 构建是自动出来的。 每次提交代码 CI 自动打包,打完还要人工去点一下上传,这一步就该顺手接上。
  • 上传要交给别人跑。 外包、代运营、多个 App 轮流发版,写成脚本比口头交代点哪个按钮靠谱。
  • 在没有图形界面的机器上。 比如公司的构建服务器,只有一个远程终端。

如果只是偶尔手动发一版,图形界面点两下反而更快,命令行的价值在于重复执行。

开始之前

一、你得先有一个已签名的包。 appuploader-cli 负责的是"把包交给苹果"这一步,不负责编译和签名。手里得先有打好的 .ipa(iOS)或 .pkg(macOS)。

二、找到这个命令。 装好开心上架之后,它已经在安装目录里了:

系统 位置
Windows 主程序 AppUploader.exe 同目录下的 appuploader-cli.exe
macOS AppUploader.app/Contents/Resources/appuploader-cli

在这个目录里打开终端就能直接用。嫌每次都要 cd 过去麻烦,把这个目录加进系统 PATH 就行(Windows 上:设置 → 系统 → 关于 → 高级系统设置 → 环境变量 → 选中 Path → 编辑 → 新建,粘贴目录路径,然后重开一个终端才生效)。

先敲一条确认能用:

bash 复制代码
appuploader-cli --help

能列出 uploadinfo 就说明找对了。

第一次上传:用 App 专用密码

命令行登录苹果,最省事的凭据是 App 专用密码 ------苹果专门为第三方工具准备的,不会触发双重验证,不用每次上传都去手机上点确认。

先去生成一个:登录 account.apple.com/account/man...,找到「App 专用密码」,新建一个。会得到形如 abcd-efgh-ijkl-mnop 的密码,只显示这一次,当场复制存好。

然后:

bash 复制代码
appuploader-cli upload -f Payload.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop

三个参数:-f 是包的路径(也可以省掉 -f,直接把路径写在命令最后),-u 是 Apple ID 邮箱,-p 就是刚才那个专用密码。

-p 一定填专用密码,不是你平时登录 Apple ID 用的密码。专用密码固定是 16 个字母、中间三个连字符,长得和登录密码完全不一样,对一眼就知道有没有填错。

回车之后,进度会一行行打出来,几分钟后看到这一行就成功了:

markdown 复制代码
> upload finished successfully

失败的话是 > upload finished with error:,后面直接跟苹果给的原因------版本号重复、签名不匹配、Info.plist 缺字段之类,照着改就行。

写脚本时不用去读日志:失败时这条命令的退出码是非 0,判断退出码就够了。

传完先别急着去 App Store Connect 找构建版本------苹果收下包之后还要处理几分钟到几十分钟,处理完它才会出现在「构建版本」列表里,那之后才能选来提审。

传 macOS / tvOS / visionOS 的包

默认按 iOS 处理,其他平台加 --type

bash 复制代码
appuploader-cli upload -u dev@example.com -p abcd-efgh-ijkl-mnop --type osx App.pkg

可选的就四个值:ios(默认)、osxappletvosxros(visionOS)。

团队和 CI:换成 API 密钥登录

上一节的做法有个问题:命令里带着某个人的 Apple ID 和密码。这个人离职、改密码、或者干脆不愿意把自己的账号交给流水线,整条自动化就断了。

团队场景更合适的是 App Store Connect API 密钥:它属于团队而不是个人,可以单独吊销,也不涉及双重验证。

appstoreconnect.apple.com/access/api 生成一把(需要 Account Holder 或 Admin 权限),拿到三样东西:

  • 密钥 ID(一串大写字母数字)
  • Issuer ID (页面上方,形如 69a6de78-4459-47e3-e053-5b8c7c11a4d1
  • .p8 私钥文件 ------只能下载这一次,苹果不给第二次机会,立刻存好

然后把 -u -p 换成这三个参数:

bash 复制代码
appuploader-cli upload -f Payload.ipa \
  --api-key UK29KBAX9X \
  --api-issuer 69a6de78-4459-47e3-e053-5b8c7c11a4d1 \
  --private-key AuthKey_UK29KBAX9X.p8

两种登录方式只能选一种,混着写会被直接拒绝。

用 API 密钥前确认两件事:

  1. 只能传 .ipa 这种方式要靠包里的 Bundle ID 去 App Store Connect 上找到对应的 App,.pkg 里取不到。macOS 的包还是用上一节的 App 专用密码。
  2. 这个 Bundle ID 得先在 App Store Connect 里建过 App。 没建的话会提示 no App Store Connect app found for bundle ID,先去建 App 再传。

写进流水线

思路就一句话:构建产出 IPA 之后加一步 upload,账号密码走 CI 自带的 Secret,别明文写进脚本。

GitHub Actions(把三样东西存成仓库 Secret):

yaml 复制代码
- name: 上传到 App Store Connect
  env:
    ASC_KEY_ID:    ${{ secrets.ASC_KEY_ID }}
    ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
    ASC_P8:        ${{ secrets.ASC_P8 }}
  run: |
    printf '%s' "$ASC_P8" > AuthKey.p8
    appuploader-cli upload -f build/App.ipa \
      --api-key "$ASC_KEY_ID" \
      --api-issuer "$ASC_ISSUER_ID" \
      --private-key AuthKey.p8
    rm -f AuthKey.p8

最后那行 rm 别省,私钥不要留在 Runner 的工作目录里。

Jenkins / 通用 Shell

bash 复制代码
set -e   # 上传失败就中断整条流水线
appuploader-cli upload -f "$WORKSPACE/build/App.ipa" \
  -u "$APPLE_ID" -p "$APP_SPECIFIC_PASSWORD"

Windows 批处理 / PowerShell

powershell 复制代码
& "C:\Program Files\AppUploader\appuploader-cli.exe" upload `
  -f .\build\App.ipa -u $env:APPLE_ID -p $env:APP_PASSWORD
if ($LASTEXITCODE -ne 0) { throw "上传失败" }

整个过程不需要 Mac,构建机是 Windows 也照样跑得完这一步。

顺带一提:只想检查包,不想上传

还有个 info 命令,不联网上传,只在本地读包,把 App Store 需要的资源描述文件 AppStoreInfo.plist 生成出来:

bash 复制代码
appuploader-cli info -u dev@example.com Payload.ipa -o AppStoreInfo.plist

两个用处:上传前先确认包本身没问题(生成成功会打印出包的 Bundle ID 和大小,可以顺手核对传的是不是那个包);或者你的流程里用的是别的上传工具,只缺这份 plist。

不加 -o 就直接打印到终端,方便接管道。info 只支持 -u 这一种账号写法,没有 API 密钥模式。

相关推荐
想要成为糕糕手1 小时前
🚀 NestJS 完全入门指南: Module/Controller/Service讲解 + 实战 CRUD
后端·nestjs
倾颜1 小时前
Node.js 为什么会慢?从 Event Loop、CPU、内存到并发控制聊性能问题
后端
用户921080262861 小时前
AI 应用平台为什么要拆分 Java 后端和 Python AI 服务
后端
星火10241 小时前
【LangChain4j系列08】Agentic AI 多智能体协作
人工智能·后端
凤山老林1 小时前
零停机数据库演进:Spring Boot 集成 Flyway 与平滑DDL变更策略
数据库·spring boot·后端
步行cgn1 小时前
MyBatis <sql> 标签详解:SQL 片段的定义与复用
后端
想要成为糕糕手1 小时前
🏭 设计模式之工厂模式:从蜜雪冰城到 NestJS,把「new」外包出去
后端·nestjs
抓哇小菜鸡1 小时前
Spring Boot + 本地大模型(Ollama/DeepSeek) + MyBatis-Plus 企业级智能体数据分析系统从零到一源码全解析
spring boot·后端·mybatis
星火10241 小时前
【LangChain4j系列07】结构化输出与类型安全
人工智能·后端