共计 3928 个字符,预计需要花费 10 分钟才能阅读完成。
在 iOS 开发过程中,经常需要将 ipa 文件上传至 App Store Connect 以供提交 testflight 交付测试。在 CI 持续集成过程中,我们可以使用 Transporter 工具来自动化上传 ipa 文件的过程。
Transporter 是 Apple 推出的基于 Java 的命令行工具,用于进行大批量交付。你可以使用 Transporter 将内容的 Store 数据包交付至 Apple TV、iTunes Store、Apple Books 和 App Store。
1 安装和配置 Transporter
Mac OS 电脑可以从 App Store 中搜索 Transporter
并安装。通过该方式安装后可从如下位置访问命令行工具 iTMSTransporter
:
/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter
然后配置环境变量以便可全局访问到该命令。编辑 .bash_profile
文件,添加以下命令内容:
export TRANSPORTER_HOME=/Applications/Transporter.app/Contents/itms
export PATH=${PATH}:${TRANSPORTER_HOME}/bin
最后,可任意位置打开终端,执行如下命令测试是否安装成功:
iTMSTransporter -version
Transporter 支持多系统。关于 Windows 和 Linux 系统上的安装,可以参考该文档:安装 Transporter
也可以直接下载安装包安装:
2 准备工作
2.1 生成 App 专用密码
打开该地址并登录:https://appleid.apple.com,然后在 App 专用密码
处创建一个用于上传专用的密码。该密码只显示一次,注意复制并及时保存。若遗忘了可移除重新创建。
2.2 查询团队简称 shortName
执行如下命令以获取开发团队 Short Name
的值:
iTMSTransporter -m provider -u <username> -p <App专用密码>
3 使用 iTMSTransporter 命令行上传 IPA
3.1 命令行上传
上传的命令格式参考:
# 基本格式
iTMSTransporter -m upload -u <username> -p <App专用密码> -assetFile <ipa路径> -asc_provider <团队ShortName> -v informational
# 推荐方式示例:
export APPLEID_PASSWORD=xxx-xxx-xxx-xxx
iTMSTransporter -m upload -u xx@lzw.me -p @env:APPLEID_PASSWORD -assetFile lzw.me.ipa -asc_provider LZWME -v informational
各参数说明参考:
-m
指定执行模式,此处固定为upload
-u
指定 Apple ID 用户名,应是邮箱格式-p
指定 APP 专用密码。为避免 CI 流程中打印到日志而泄露密码,可以设置到环境变量$APPLEID_PASSWORD
进行赋值,然后此处固定为@env:APPLEID_PASSWORD
-assetFile
指定要上传的 IPA 文件路径-asc_provider
指定所属团队的Short Name
-v
指定日志级别。可选值为:off | informational | critical | detailed | eXtreme
如果你因为公司网络安全管理的限制,CI 流程中禁止访问外部网络而不能直接上传,则需要人工介入,下载产物后手工上传。此时可能会使用 Windows 或 Linux 系统。
需要注意,使用非 Mac OS
系统上传,还需要指定 -assetDescription <AppStoreInfo.plist>
参数。AppStoreInfo.plist
文件可以在产出 ipa 文件之后生成。生成命令参考:
xcrun swinfo -o AppStoreInfo.plist -prettyprint true --plistFormat binary -f xxx.ipa
3.2 一个基于 Node.js 的上传函数封装
用于 CLI 程序中的函数参考,基于 Node.js 和 TypeScript:
/**
* transporter 上传相关的参数
* @see https://help.apple.com/itc/transporteruserguide/zh_CN.lproj/static.html
*/
export type ITransporter = Record<string, string> & {
/** 指定 ipa 文件 */
assetFile?: string;
/** appleid 账号 */
u?: string;
/** App 专用密码。设置到环境变量 APPLE_APP_PWD */
p?: string;
/** 日志级别 */
v?: 'off' | 'critical' | 'informational' | 'detailed' | 'eXtreme';
/** 用于记录输出信息的目录和文件名,包括时间戳 */
o?: string;
/** 团队简称 */
asc_provider?: string;
};
export function uploadTransporter(args: ITransporter = {}) {
if (!args.assetFile || !args.assetFile.endsWith('.ipa') || !existsSync(args.assetFile)) {
logger.error(`[uploadTransporter] 请传入正确的 ipa 文件`, args.assetFile);
return false;
}
const dirs = [
'/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter',
'/Applications/Xcode.app/Contents/Developer/usr/bin/iTMSTransporter',
'/usr/local/itms/bin',
];
const transporterPath = dirs.find(dir => existsSync(dir));
if (!transporterPath) {
logger.error('[uploadTransporter] 未找到上传工具 iTMSTransporter,请通过 App Store 安装 Transporter 应用!');
return false;
}
const mbdpConfig = getMbdpConfig();
args = {
m: 'upload',
assetFile: args.assetFile,
...mbdpConfig.ios.transporter!,
...args,
};
if (!args.p || !args.u) {
logger.warn(`[uploadTransporter] 请指定 u 和 p 参数`);
return false;
}
const params = Object.entries(args).map(([k, v]) => {
if (k === 'p') {
process.env.APPLE_APP_PWD = v;
return `-p @env:APPLE_APP_PWD`;
} else return `-${k} "${v}"`;
});
const cmd = [transporterPath, ...params].join(' ');
logger.info('开始上传 ipa 至 App Store Connect');
execSync(cmd);
return true;
}
4 遇到的问题及解决参考
4.1 问题:认证失败返回 401
日志报错如下:
Error Messages:
could not find a provider public id: actorsGetCollection call failed with: 401 - {
"errors": [{
"status": "401",
"code": "NOT_AUTHORIZED",
"title": "Authentication credentials are missing or invalid.",
"detail": "Provide a properly configured and signed bearer token, and make sure that it has not expired. Learn more about Generating Tokens for API Requests https://developer.apple.com/go/?id=api-generating-tokens"
}]
}
设置 -asc_provider
参数即可。该参数默认是可选的,但当自己的账号加入了多个团队时,必须指定该参数。
4.2 问题:-asc_provider
参数设置后报错
主要日志报错如下:
ERROR: The username xx@lzw.me is not a member of the provider xxxx. Contact you team admin for assistance.(1296)
由于参考了其他文章,将其取值设置为了团队ID。当前此处应当设置为团队 Short Name
简称。获取方法为:
iTMSTransporter -m provider -u <username> -p <App专用密码>
5 相关参考