Transporter 命令行上传 ipa 至 App Store Connect 的方法

目录
[隐藏]

在 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 相关参考

点赞 (1)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code