【免费下载链接】App-Store-Connect-CLI
Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more
本文以 App-Store-Connect-CLI 仓库中的设计文档 docs/design/app-store-connect-api-4.4.1-subscription-sparse-fields.md 为骨架,讲解该 CLI 如何跟随 Apple App Store Connect API 4.4.1 版本为订阅(subscription)相关资源新增稀疏字段(sparse fields)查询能力:涉及 17 个 GET 端点、fields[subscriptions]等参数枚举的扩展、CLI 标志命名约定、自动 include 关系注入,以及--next冲突等失败语义。读完本文,你将掌握这些新增标志的准确用法、底层参数校验机制,以及如何用源码与测试验证该功能的完整行为。
切片定位:不新增命令名词,只扩展既有命令
这一特性切片没有引入任何新的命令名词,而是扩展既有的订阅资源、优惠(offer)、定价(pricing)、审核(review)与推广购买(promoted-purchase)命令。唯一一个此前仅存在于客户端库(client-only)的关系读取,通过既有的 scoped view 命令对外暴露:
asc subscriptions promoted-purchases view --app "APP_ID" --subscription-id "SUBSCRIPTION_SELECTOR"从源码看,该命令由 internal/cli/subscriptions/promoted_purchases.go 中的SubscriptionsPromotedPurchasesCommand()构建,它复用了通用推广购买命令树,并通过ConfigureScopedPromotedPurchasesCommand注入订阅专属的路径前缀(asc subscriptions promoted-purchases)、产品类型(SUBSCRIPTION)与选择器解析逻辑。
相关资源稀疏字段的 CLI 标志命名
新增的稀疏字段标志遵循 CLI 既有的命名约定,每个标志精确对应 OpenAPI 中的一个fields[...]查询参数:
--subscription-fields映射fields[subscriptions]--group-fields映射fields[subscriptionGroups]--price-point-fields映射fields[subscriptionPricePoints]--iap-fields映射fields[inAppPurchases]--fields仍是主资源稀疏字段标志,用于subscriptions pricing price-points view
在本次变更之前,受影响的命令没有任何途径请求这些新增的稀疏字段值:列表命令虽然接受--next,但若将新稀疏字段选项塞进一个不透明的 next URL 中,会被静默丢弃——这正是本切片要修复的痛点。
OpenAPI 契约与实现台账:17 个端点的稀疏字段新增
Apple 的 4.4.1 schema 对本切片只改动下述参数枚举,所有操作仍返回既有的 JSON:API 响应类型。设计文档给出的完整台账如下,全部 17 项均已在仓库中实现(标记为[x]):
| Done | Method and path | 4.4.1 sparse-field addition | CLI command |
|---|---|---|---|
| [x] | GET /v1/subscriptionAppStoreReviewScreenshots/{id} | fields[subscriptions]=versions | subscriptions review screenshots view |
| [x] | GET /v1/subscriptionGroupLocalizations/{id} | fields[subscriptionGroups]=versions | subscriptions groups localizations view |
| [x] | GET /v1/subscriptionImages/{id} | fields[subscriptions]=versions | subscriptions images view |
| [x] | GET /v1/subscriptionLocalizations/{id} | fields[subscriptions]=versions | subscriptions localizations view |
| [x] | GET /v1/subscriptionOfferCodes/{id} | fields[subscriptions]=versions | subscriptions offers offer-codes view |
| [x] | GET /v1/subscriptionPricePoints/{id} | fields[subscriptionPricePoints]=adjustedEqualizations | subscriptions pricing price-points view |
| [x] | GET /v1/subscriptionPromotionalOffers/{id} | fields[subscriptions]=versions | subscriptions offers promotional view |
| [x] | GET /v1/subscriptionGroups/{id}/subscriptionGroupLocalizations | fields[subscriptionGroups]=versions | subscriptions groups localizations list |
| [x] | GET /v1/subscriptionOfferCodes/{id}/prices | fields[subscriptionPricePoints]=adjustedEqualizations | subscriptions offers offer-codes prices |
| [x] | GET /v1/subscriptionPromotionalOffers/{id}/prices | fields[subscriptionPricePoints]=adjustedEqualizations | subscriptions offers promotional prices |
| [x] | GET /v1/subscriptions/{id}/appStoreReviewScreenshot | fields[subscriptions]=versions | subscriptions review app-store-screenshot view |
| [x] | GET /v1/subscriptions/{id}/images | fields[subscriptions]=versions | subscriptions images list |
| [x] | GET /v1/subscriptions/{id}/introductoryOffers | fields[subscriptions]=versions;fields[subscriptionPricePoints]=adjustedEqualizations | subscriptions offers introductory list |
| [x] | GET /v1/subscriptions/{id}/offerCodes | fields[subscriptions]=versions | subscriptions offers offer-codes list |
| [x] | GET /v1/subscriptions/{id}/promotedPurchase | fields[subscriptions]=versions;fields[inAppPurchases]=versions | subscriptions promoted-purchases view --subscription-id |
| [x] | GET /v1/subscriptions/{id}/promotionalOffers | fields[subscriptions]=versions | subscriptions offers promotional list |
| [x] | GET /v1/subscriptions/{id}/subscriptionLocalizations | fields[subscriptions]=versions | subscriptions localizations list |
其中两个端点同时获得两个稀疏字段参数:/v1/subscriptions/{id}/introductoryOffers同时携带fields[subscriptions]与fields[subscriptionPricePoints];/v1/subscriptions/{id}/promotedPurchase同时携带fields[subscriptions]与fields[inAppPurchases]。
台账的源码级印证
这份台账并非纸面设计,仓库用测试将其固化。在 internal/asc/subscription_sparse_fields_4_4_1_test.go 中:
TestSubscriptionSparseFields441OpenAPILedger直接读取 docs/openapi/latest.json(Apple OpenAPI 4.4.1 schema 的仓库镜像),逐路径断言每个fields[...]参数枚举包含预期值(如versions、adjustedEqualizations),并硬校验台账路径数为 17;TestSubscriptionSparseFields441ExactQueries对 14 种调用组合断言最终发出的 HTTP GET 请求路径与查询串完全精确,例如 introductory offers 的查询串为fields[subscriptions]=versions&fields[subscriptionPricePoints]=adjustedEqualizations&include=subscription,subscriptionPricePoint。
这意味着只要 schema 或客户端实现发生漂移,测试就会先行失败(RED)。
参数校验与自动 include:客户端创建前的严格把关
CLI 在创建客户端之前,会针对每个资源类型的确切枚举校验每一个稀疏字段值;非法值会以 usage error 形式在发起任何 HTTP 请求之前被拒绝。相关稀疏字段会自动添加其所需的 include 关系,共四类:
subscription(fields[subscriptions]触发)subscriptionGroup(fields[subscriptionGroups]触发)subscriptionPricePoint(fields[subscriptionPricePoints]触发)inAppPurchaseV2(fields[inAppPurchases]触发)
自动 include 的实现集中在 internal/cli/subscriptions/sparse_fields_4_4_1.go:includeRelationshipForFields在字段非空时返回对应的关系名,appendIncludeForFields负责去重追加,避免重复 include。
可请求的枚举值同样由源码直接给出,例如subscriptionPricePointFieldsList()返回customerPrice、proceeds、proceedsYear2、territory、equalizations、adjustedEqualizations——其中adjustedEqualizations正是本次 4.4.1 新增值;subscriptionFieldsList()(见 internal/cli/subscriptions/version_selections.go)则覆盖name、productId、familySharable、marketSettings、multiSeatStatus、state、subscriptionPeriod、reviewNote、groupLevel以及各类关系字段与本次新增的versions等 23 个取值。
分页命令的 --next 互斥规则
在分页命令上,显式传入稀疏字段标志与--next冲突:此时不透明的 next URL 是唯一的查询来源,不允许再叠加其他查询选项。这一规则由两个辅助函数落实:
normalizeSparseFieldsFlag:当--next非空且某稀疏字段标志被显式提供时,返回--next cannot be combined with --<name>的 usage error;validateNextExclusiveFlags:批量校验多个标志名与--next的互斥关系。
测试 internal/asc/subscription_sparse_fields_4_4_1_test.go 中的TestSubscriptionSparseFields441NextURLConflictsBeforeHTTP覆盖了 localizations、images、introductory offers、promotional offers、offer codes、price points 等 8 个场景,并断言冲突错误消息包含"next URL cannot be combined with query options"且 HTTP 请求数为 0——即冲突在客户端层就被拦截,绝不触网。
双选择器:promoted-purchases view 的扩展语义
subscriptions promoted-purchases view接受且仅接受以下两种选择器之一:
- 既有的
--promoted-purchase-id(直接指定推广购买资源 ID),或 - 新增的
--subscription-id(订阅选择器)
新增的--subscription-id选择器接受三种输入形式:ASC 订阅 ID、产品 ID(product ID),或精确的当前名称;其中产品 ID 与名称形式要求同时提供--app或设置ASC_APP_ID环境变量。既有的直接资源调用方式保持不变。
两种选择器形式都接受完全相同的推广购买稀疏字段,因为两个底层端点暴露的是同一套fields[inAppPurchases]、fields[subscriptions]与 include 契约。从 internal/cli/subscriptions/promoted_purchases.go 可以看到,ResolveOwnerID通过resolveSubscriptionLookupIDWithTimeout将订阅选择器解析为订阅 ID,随后调用client.GetSubscriptionPromotedPurchase完成查询。该文件的命令长帮助信息中也给出了完整的示例:
asc subscriptions promoted-purchases list --app "APP_ID" asc subscriptions promoted-purchases view --promoted-purchase-id "PROMO_ID" asc subscriptions promoted-purchases view --app "APP_ID" --subscription-id "SUBSCRIPTION_SELECTOR" asc subscriptions promoted-purchases create --app "APP_ID" --product-id "SUB_ID" --visible-for-all-users true asc subscriptions promoted-purchases update --promoted-purchase-id "PROMO_ID" --enabled false asc subscriptions promoted-purchases delete --promoted-purchase-id "PROMO_ID" --confirm asc subscriptions promoted-purchases link --app "APP_ID" --promoted-purchase-id "PROMO_ID"响应模型中的 adjustedEqualizations
在客户端数据模型层面,adjustedEqualizations对应 internal/asc/subscriptions.go 中SubscriptionPricePointResponseRelationships的AdjustedEqualizations字段,类型为SubscriptionPricePointLinkRelationship——该类型只包含links、不包含资源 linkage 数据,与 OpenAPI 中该关系仅返回链接的契约一致。
输出、兼容性与失败行为
本切片的输出行为完全保持既有实现,不引入新的输出格式:
- 输出仍为感知 TTY 的 JSON / 表格 / Markdown 三种形态;
- 成功读取以退出码 0 结束;
- 非法稀疏字段、显式空值、缺失 ID、以及
--next冲突均属于 usage error(退出码 2),且在鉴权与 HTTP 请求之前就写入 stderr; - 既有的调用方式与响应模型完全不变;
- 已废弃的 v1 localization 与 image 命令仍保留,并维持既有告警。
这些退出码语义与命令层校验逻辑在仓库的命令测试与 cmd/exit_codes.go 体系中是一致的。显式空值(如--subscription-fields ""且标志被显式提供)会触发normalizeSelectionFlag中的--<name> must not be empty错误,见 internal/cli/subscriptions/version_selections.go。
验证计划与工程质量门禁
设计文档为本切片定义了五步验证计划,仓库中的测试基建与之对应:
- RED 先行:用客户端 HTTP 测试覆盖全部 17 个路径,并为每个新标志族(自动 include、非法枚举、显式空值、
--next冲突)建立 CLI 测试; - 端点专属实现:实现端点专属的 option 类型与查询构建器,不使用通用的 query map;
- 聚焦测试:运行
internal/asc、internal/cli/subscriptions与cmdtest相关包,然后构建二进制检查变更后的 help 输出与退出行为; - 文档同步:重新生成 docs/COMMANDS.md,并跑 format、docs、lint 与完整测试门禁;
- 只读冒烟:仅在具备凭据与合适既有资源时,执行一次安全的只读 App Store Connect 冒烟测试,无需任何在线变更。
备选方案与设计权衡
文档记录了两种被否决的备选实现,其取舍值得借鉴:
- 原生
map[string]string查询逃生舱:实现更小,但会接受不支持的参数,且会静默偏离端点专属的 OpenAPI 事实。因此被否决。 - 跨无关操作复用一个通用函数式 option 类型:会在编译期允许非法的跨端点参数组合。因此被否决,实现改为保持端点专属的 option 类型,仅在三个查询契约完全一致的 promoted-purchase 端点间共享同一 option 类型。
这种“端点专属、契约共享最小化”的设计与 internal/asc/subscription_sparse_fields_4_4_1_test.go 中WithSubscriptionOfferCodePricesPricePointFields、WithSubscriptionIntroductoryOffersSubscriptionFields等大量With*Fieldsoption 一一对应,验证了每一份 option 类型都精确绑定其端点查询契约。
小结
本切片以最小的命令面改动,将 App Store Connect API 4.4.1 新增的订阅稀疏字段能力完整接入 CLI:17 个端点逐一落实、标志命名与既有约定一致、枚举校验与自动 include 前置到客户端创建之前、--next冲突显式报错,且全程保持既有输出与退出码语义不变。若需深入,可从 docs/design/app-store-connect-api-4.4.1-subscription-sparse-fields.md 出发,对照 internal/asc/subscription_sparse_fields_4_4_1_test.go、internal/cli/subscriptions/sparse_fields_4_4_1.go 与 internal/cli/subscriptions/promoted_purchases.go 追踪完整实现链路。
【免费下载链接】App-Store-Connect-CLI
Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more
相关推荐
ScyllaDB 集群缩容实操:使用 nodetool decommission 与 removenode 安全移除节点
ScyllaDB 集群缩容实操:使用 nodetool decommission 与 removenode 安全移除节点 ScyllaDB 集群在业务增长或收缩
gsplat 光栅化基础算子详解:3DGS 与 2DGS 底层 CUDA 函数 API 全解析
gsplat 光栅化基础算子详解:3DGS 与 2DGS 底层 CUDA 函数 API 全解析 本文系统讲解 gsplat 中支撑光栅化(rasterizati
如何固定OpenRAG的Langflow版本?LANGFLOW_VERSION配置完整指南
如何固定OpenRAG的Langflow版本?LANGFLOW_VERSION配置完整指南 OpenRAG 是基于 Langflow、Docling 和 Ope
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考