obelisk-cli
v0.5.2
Published
manifest-to-App-Store 全自动上架工具。以 `obelisk/manifest.json` 为唯一数据源,覆盖从截图采集、官网同步到 App Store Connect 提审的完整上架流程。
Readme
Obelisk CLI
manifest-to-App-Store 全自动上架工具。以 obelisk/manifest.json 为唯一数据源,覆盖从截图采集、官网同步到 App Store Connect 提审的完整上架流程。
直接调用 ASC REST API,不依赖 fastlane。
安装
npm install -g obelisk-cli
# 或项目内使用
npx obelisk-cli <command>要求 Node.js >= 20。
快速开始
新项目初始化
cd your-app-project
obelisk init交互式创建:
obelisk/manifest.json— 所有上架信息的唯一数据源.github/workflows/appstore-publish.yml— 一键发布 workflow.github/workflows/validate-manifest.yml— PR 校验 workflow.github/workflows/sync-obelisk.yml— 官网同步 workflow
已有项目接入
在项目根目录创建 obelisk/manifest.json,参考下方 Manifest 完整字段。
命令
obelisk validate
离线校验 manifest,不调用任何 API。
obelisk validate --manifest obelisk/manifest.json校验规则:
id格式(小写字母 + 数字 + 连字符)appStore.appId、bundleId非空- 每个 locale 的
keywords<= 100 字符 - 每个 locale 的
subtitle<= 30 字符 reviewContact字段完整docs.privacy和docs.support非空(Apple 必填)
历史拒审防御规则(基于本仓库的真实拒审记录,详见 docs/rejection-history.md):
- subtitle / promotionalText / keywords 不能含价格字眼
free / 免费 / discount / 折扣 / sale / cheap / $0等(Apple 2.3.7) - 含 AUTO_RENEWABLE 订阅或 IAP 时:
- 每个产品必须配置
reviewScreenshot(Apple 2.1(b) — 没有 IAP 截图 Apple 拒绝审核 IAP) appStore.eulaUrl(可选)若设置必须是合法 URL;未设置时使用 Apple 标准 EULA URL(stdeula)- submit 阶段会自动把
Terms of Use (EULA): {url}+Privacy Policy: {url}追加到 App Description(Apple 3.1.2(c))
- 每个产品必须配置
- IAP 推广截图不可重复(Apple 2.3.2 — 多个产品共用同一张图直接拒)
obelisk archive
封装 xcodebuild archive,产出 .xcarchive(用作 preflight / upload 的输入)。
obelisk archive --scheme nota --configuration Release
# 自定义 archive 路径
obelisk archive --archive-path /tmp/nota.xcarchive最后一行 stdout 打印 archive path,便于 pipe:arch=$(obelisk archive ... | tail -1)。
obelisk upload
Apple 官方 macOS 上传链路。用 xcodebuild -exportArchive 配合 destination=upload,等价于 Xcode Organizer 里点 "Distribute App → App Store Connect → Upload",直接把 archive 上传到 App Store Connect。
obelisk upload --archive-path /tmp/nota.xcarchive为什么不用 xcrun altool --upload-package? 对 macOS pkg,altool 经常报 "UPLOAD SUCCEEDED" 但 ASC 后端不接收(silent fail),build 永远不出现在 builds 列表里。这是 macOS 分发领域的常见坑,obelisk 只走 xcodebuild -exportArchive destination=upload 这条 Apple 官方推荐路径。
从 manifest 读 appStore.appId + bundleId。ASC API 凭证解析优先级:--key-id / --issuer-id / --key > OBELISK_ASC_KEY_* 环境变量 > ~/.appstoreconnect/private_keys/api_key.json。
自动从 keychain 发现 Apple Distribution + 3rd Party Mac Developer Installer 证书 SHA1,扫描 ~/Library/MobileDevice/Provisioning Profiles 匹配 *_AppStore 描述文件,生成 manual signing + SHA1 指纹的 exportOptions.plist(避免 Xcode 26.x "Cloud signing permission error" 误匹配)。
obelisk preflight
App Store 提交前的通用二进制校验:
Main app + 所有扩展必须带
PrivacyInfo.xcprivacyApp Sandbox 必须开启
get-task-allow必须为 false(--strict模式下 true 直接 fail)aps-environment匹配--expect-push-env(production / development)过宽 entitlement 警告(默认 warn,
--strict升级为 fail;用--allow-entitlement <key>显式认可):com.apple.security.files.downloads.{read-only,read-write}com.apple.security.files.{pictures,music,movies}.{read-only,read-write}com.apple.security.network.server
Apple 2.4.5(i) 要求声明的 entitlement 必须有对应功能;SnapPro v1.0 因此被拒。
HealthKit + iCloud 组合直接 fail(Apple 5.1.3(ii) 禁止 health data 写 iCloud;SipLog 因此被拒 2 次)
# 从 xcarchive 自动定位 Products/Applications/*.app
obelisk preflight --archive-path /tmp/nota.xcarchive --strict --expect-push-env production
# 或直接指定 .app
obelisk preflight --app-path ~/Library/Developer/Xcode/DerivedData/.../Nota.app
# 多个 entitlement 显式放行
obelisk preflight --archive-path /tmp/x.xcarchive \
--allow-entitlement com.apple.security.network.server \
--allow-entitlement com.apple.security.files.downloads.read-write不含项目特化检查(比如某些产品的 OneDriveOfficialConfig.plist 占位符拦截)。那些属于业务约束,应在项目自己的 Makefile 里串一条独立脚本。
标准发布流水线
# 1. 归档
arch=$(obelisk archive --scheme nota | tail -1)
# 2. 本地二进制校验
obelisk preflight --archive-path "$arch" --strict --expect-push-env production
# 3. Apple 官方链路上传到 ASC
obelisk upload --archive-path "$arch"
# 4. ASC 处理完后加入内测组
obelisk tf:internal
# 或: 完成元数据提审
obelisk submit --version 1.0.0obelisk screenshots
自动截图:调度 Xcode UI Test,遍历设备和语言,从 xcresult 提取截图。
obelisk screenshots --manifest obelisk/manifest.json
# 只截指定语言
obelisk screenshots --manifest obelisk/manifest.json --locale en,zh需要在 manifest 中配置 appStore.screenshots.scheme(UI Test scheme 名)和 appStore.screenshots.devices。
输出目录:
obelisk/screenshots/app/
en/
6.7/1-main.png, 2-settings.png, ...
6.1/...
desktop/...
zh/
6.7/...App 侧 UI Test 示例:
import XCTest
class ScreenshotTests: XCTestCase {
func testCapture() {
let app = XCUIApplication()
app.launch()
// 截图 1
let screenshot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = "1-main"
attachment.lifetime = .keepAlways
add(attachment)
// 截图 2
app.buttons["Settings"].tap()
let screenshot2 = XCUIScreen.main.screenshot()
let attachment2 = XCTAttachment(screenshot: screenshot2)
attachment2.name = "2-settings"
attachment2.lifetime = .keepAlways
add(attachment2)
}
}obelisk sync
同步 manifest 到 obelisk.club 官网,触发 GitHub repository_dispatch。
obelisk sync --manifest obelisk/manifest.json需要环境变量 OBELISK_SYNC_PAT(GitHub PAT,有 obelisk.club 仓库的 write 权限)。
同步后自动生成的页面:
https://obelisk.club/apps/{id} — 产品页
https://obelisk.club/apps/{id}/privacy — 隐私政策
https://obelisk.club/apps/{id}/support — 支持页
https://obelisk.club/apps/{id}/terms — 服务条款obelisk submit
核心命令。通过 ASC API 完成全流程提审。
obelisk submit \
--manifest obelisk/manifest.json \
--key-id YOUR_KEY_ID \
--issuer-id YOUR_ISSUER_ID \
--key path/to/AuthKey.p8 \
--version 1.1.0
# 跳过截图上传
obelisk submit ... --skip-screenshots
# metadata-only sync: build 还没上传时先把 manifest 改动推到 ASC
# (跳过 waitForBuild / attachBuild / screenshots / submitForReview)
obelisk submit ... --skip-build-wait
# 预览模式(不实际调用 API)
obelisk submit ... --dry-run执行流程:
1. Pre-flight 校验 manifest + 验证 API Key
2. Wait for Build 查找/等待 VALID 状态的 build
3. Version 创建或复用 App Store 版本
4. Localizations 同步所有语言的 metadata + URL
(订阅 app 自动追加 EULA + Privacy 链接到 description)
5. Screenshots 增量上传截图(checksum 比对,未变化不重传)
6. Review Details 更新审核联系人和备注
7. App Info 设置 copyright、分类、app 名称、subtitle
8. IAP 上传 IAP/订阅审核截图
9. First-submit gate 仅当 app 从未通过审核时检查 reviewNotes 完整度
(要求 demo/录屏/用途,可用 --skip-first-submission-check 绕过)
10. Submit 创建 reviewSubmission 并提交,**包含 APP_STORE_VERSION
+ READY_TO_SUBMIT/REJECTED/DEVELOPER_ACTION_NEEDED 状态的
subscription 与 IAP 一起提审**(Apple 2.1(b) 要求 IAP 必须
随 binary 一起提交)版本状态处理:
| 状态 | 行为 | |------|------| | PREPARE_FOR_SUBMISSION | 全流程(build + 截图 + metadata + 提审) | | REJECTED | 全流程 + 尝试 resubmit 被拒的 submission | | WAITING_FOR_REVIEW | 仅更新可修改的 metadata | | IN_REVIEW | 仅更新可修改的 metadata | | READY_FOR_DISTRIBUTION | 报错(需要创建新版本号) |
obelisk tf:internal
推送 build 到 TestFlight 内部测试组。无需 Beta App Review,build 处理完即可分发。
obelisk tf:internal \
--manifest obelisk/manifest.json \
--key-id YOUR_KEY_ID \
--issuer-id YOUR_ISSUER_ID \
--key path/to/AuthKey.p8
# 临时覆盖默认组名
obelisk tf:internal --groups "Hotfix Testers,Smoke"
# 指定 build number
obelisk tf:internal --build-number 142执行流程:
1. Pre-flight 校验 manifest + 验证 API Key
2. Wait for Build 查找/等待 VALID 状态的 build
3. Encryption declare encryption compliance(按需)
4. Resolve Groups 在 ASC 中查找 internal beta group
5. Add to Groups 把 build 加入指定组(重复加入幂等)需要在 ASC 网页端预先创建 internal beta group。
obelisk tf:external
推送 build 到 TestFlight 外部测试组,自动提交 Beta App Review。
obelisk tf:external \
--manifest obelisk/manifest.json \
--key-id YOUR_KEY_ID \
--issuer-id YOUR_ISSUER_ID \
--key path/to/AuthKey.p8
# 只加组不提审(先内部 dogfood 再决定)
obelisk tf:external --skip-submit执行流程:
1. Pre-flight 校验 manifest + 验证 API Key
2. Wait for Build 查找/等待 VALID 状态的 build
3. Encryption declare encryption compliance(按需)
4. Beta App Localizations 同步 description, feedbackEmail, marketingUrl, privacyPolicyUrl
5. Beta Build Localizations 同步 whatsNew per locale
6. Beta Review Detail 同步审核联系人 + demo 账号 + notes
7. Resolve Groups 在 ASC 中查找 external beta group
8. Add to Groups 把 build 加入指定组
9. Submit Beta Review 根据 externalBuildState 决定是否新提交Beta Review 状态机(步骤 9):
| externalBuildState | 行为 |
|--------------------|------|
| READY_FOR_BETA_SUBMISSION | POST 新 submission |
| WAITING_FOR_BETA_REVIEW | 跳过 |
| IN_BETA_REVIEW | 跳过 |
| BETA_APPROVED | 跳过 |
| BETA_REJECTED | 输出旧 reject reason 后 POST 新 submission |
--skip-submit 时整个第 9 步跳过,仅完成分发。
obelisk iap:sync
从 manifest 幂等创建/同步订阅产品(组 + 产品 + 本地化 + 可售区域 + 价格 + 免费试用)。
独立于 submit —— 新 app 或新增产品时运行一次。存在的产品/组会被跳过,不会破坏性覆盖。
obelisk iap:sync \
--manifest obelisk/manifest.json \
--key-id YOUR_KEY_ID \
--issuer-id YOUR_ISSUER_ID \
--key path/to/AuthKey.p8manifest 片段(见下方 appStore.subscriptions):
{
"appStore": {
"subscriptions": [
{
"groupReferenceName": "Pro",
"groupLocalizations": {
"en-US": { "name": "Pro" },
"zh-Hans": { "name": "Pro" }
},
"products": [
{
"productId": "com.example.pro.monthly",
"referenceName": "Pro Monthly",
"familyShareable": true,
"period": "ONE_MONTH",
"localizations": {
"en-US": { "name": "Monthly", "description": "Auto-renews monthly" },
"zh-Hans": { "name": "月付", "description": "每月自动续订" }
},
"prices": { "USA": "0.99", "CHN": "8" },
"introOffer": {
"type": "FREE_TRIAL",
"duration": "ONE_WEEK",
"numberOfPeriods": 1,
"territories": ["USA", "CHN"]
}
}
]
}
]
}
}执行流程:
1. Pre-flight 校验 manifest + 验证 API Key
2. 对每个 group:
a. 创建 subscriptionGroup(如不存在)
b. 创建 groupLocalizations(缺失的语种)
3. 对每个 product:
a. 创建 subscription(设置 familyShareable / period / groupLevel)
b. 创建 subscriptionLocalizations(name + description per locale)
c. 创建 subscriptionAvailability(默认 175 territories + availableInNewTerritories)
d. 创建 subscriptionPrices(按 target 价格在每个 territory 找最近 pricePoint)
e. 创建 subscriptionIntroductoryOffers(FREE_TRIAL / PAY_AS_YOU_GO / PAY_UP_FRONT)TestFlight 配置
appStore.testFlight 节点(全部 optional):
| 字段 | 说明 |
|------|------|
| internalGroups | tf:internal 默认推送目标,字符串数组 |
| externalGroups | tf:external 默认推送目标,字符串数组 |
| feedbackEmail | 外测必填,tester 反馈邮箱 |
| betaReviewNotes | 给 Beta Review 审核员的备注(缺省 fallback 到 appStore.reviewNotes) |
| locales.{lang}.whatsNew | TF 专属更新说明(缺省 fallback 到 locales.{lang}.whatsNew) |
| locales.{lang}.description | TF 专属应用描述(缺省截取 locales.{lang}.fullDescription 前 4000 字符) |
未声明 testFlight 节点时,tf:internal / tf:external 必须用 --groups 显式传组名。
自动拒审检测
每次执行需要 ASC 凭证的命令(submit / status / tf:internal / tf:external / publish),obelisk 会顺手扫一次该 app 的 reviewSubmissions + appStoreVersions,发现新的 UNRESOLVED_ISSUES / REJECTED / METADATA_REJECTED 就在终端打 banner,并把 (appId, submissionId/versionId, state) 记到 ~/.obelisk/seen-rejections.json 避免重复警告。
跨项目共享,所有走 obelisk 提交的 app 自动登记。要静音设 OBELISK_NO_REJECTION_DETECTOR=1。
只记元数据(API 公开能拿到的状态),不自动抓 Resolution Center 拒审原文(那需要 Apple ID 网页登录)。看到 banner 之后建议:
- 打开 banner 给的 Resolution Center 链接读原文
- 把新模式追加到 docs/rejection-history.md
- 视情况在
src/manifest/guardrails.mjs/src/commands/preflight.mjs加规则 - 后续所有项目自动获得防御
obelisk status
查询当前版本在 ASC 的状态。
obelisk status \
--manifest obelisk/manifest.json \
--key-id YOUR_KEY_ID \
--issuer-id YOUR_ISSUER_ID \
--key path/to/AuthKey.p8输出示例:
[App Store Status]
App: SnapPro - 专业截图录屏工具
Bundle ID: com.peter.SnapPro
v1.1: WAITING_FOR_REVIEW (created 2026-04-12)
v1.0: READY_FOR_SALE (created 2026-03-26)
[Recent Builds]
141: VALID — 2026-04-12
139: VALID — 2026-04-02obelisk publish
一条龙命令:screenshots → sync → submit。
obelisk publish \
--manifest obelisk/manifest.json \
--key-id YOUR_KEY_ID \
--issuer-id YOUR_ISSUER_ID \
--key path/to/AuthKey.p8 \
--version 1.1.0ASC API 认证
需要三个凭据:
| 凭据 | 说明 | 获取方式 | |------|------|---------| | Key ID | 10 位字母数字 | ASC → Users and Access → Integrations → App Store Connect API | | Issuer ID | UUID 格式 | 同上页面顶部 | | AuthKey .p8 | ES256 私钥文件 | 创建 API Key 时下载(只能下载一次) |
传入方式(优先级从高到低)
1. 命令行参数
obelisk submit --key-id ABC --issuer-id 123-456 --key AuthKey.p82. 环境变量
export OBELISK_ASC_KEY_ID=ABC1234567
export OBELISK_ASC_ISSUER_ID=12345678-1234-1234-1234-123456789012
export OBELISK_ASC_KEY_FILE=~/.appstoreconnect/private_keys/AuthKey.p83. GitHub Actions Secrets
env:
OBELISK_ASC_KEY_ID: ${{ secrets.APPSTORE_KEY_ID }}
OBELISK_ASC_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}Manifest 完整字段
{
// ===== 基础信息 =====
"id": "snappro", // 产品 ID,决定官网 URL
"name": "SnapPro", // 默认显示名
"themeColor": "#FF0055", // 品牌色
"downloads": [ // 下载链接
{ "platform": "macos", "url": "https://apps.apple.com/..." }
],
// ===== App Store Connect =====
"appStore": {
"appId": "6761193994", // ASC 数字 ID
"bundleId": "com.peter.SnapPro", // Bundle ID
"platform": "macos", // macos | ios | universal
"primaryCategory": "DEVELOPER_TOOLS", // 主分类
"secondaryCategory": "PRODUCTIVITY", // 副分类(可选)
"releaseType": "manual", // manual | auto
"phasedRelease": false, // iOS 7 天渐进发布
"usesNonExemptEncryption": false, // 加密合规
"contentRights": "DOES_NOT_USE_THIRD_PARTY_CONTENT",
"copyright": "Copyright ...", // 版权声明
"eulaUrl": null, // 可选;订阅 app 强制必须有 EULA
// 链接,未设置时自动用 Apple 标准
// stdeula URL,submit 时自动注入
// App Description(Apple 3.1.2(c))
"reviewContact": { // 审核联系人
"firstName": "Peter",
"lastName": "Zhang",
"phone": "+8618612365605",
"email": "[email protected]"
},
"reviewNotes": "No sign-in required.", // 审核备注
"demoAccount": null, // { "username": "...", "password": "..." }
"screenshots": {
"scheme": "AppScreenshots", // UI Test scheme(自动截图用)
"devices": { // 设备列表(自动截图用)
"ios": ["iPhone 16 Pro Max", "iPhone 16", "iPad Pro 13-inch (M5)"],
"macos": ["My Mac"]
},
"static": { // 静态截图目录
"app": "obelisk/screenshots/app/",
"iap": "obelisk/screenshots/iap/",
"subscription": "obelisk/screenshots/subscription/"
}
},
"previewVideos": { // 预览视频(可选)
"en": "obelisk/previews/en.mp4"
},
"inAppPurchases": [ // IAP 审核截图(可选;现有 IAP)
{
"productId": "com.peter.SnapPro.pro.monthly",
"referenceName": "Pro Monthly",
"type": "AUTO_RENEWABLE",
"reviewScreenshot": "obelisk/screenshots/subscription/purchase.png"
}
],
"subscriptions": [ // 订阅 bootstrap(可选;obelisk iap:sync 使用)
{
"groupReferenceName": "Pro",
"groupLocalizations": {
"en-US": { "name": "Pro" },
"zh-Hans": { "name": "Pro" }
},
"products": [
{
"productId": "com.peter.SnapPro.pro.monthly",
"referenceName": "Pro Monthly",
"familyShareable": true, // Pro 建议 true;Pro AI 按 unit economics
"period": "ONE_MONTH", // ONE_WEEK | ONE_MONTH | TWO_MONTHS | THREE_MONTHS | SIX_MONTHS | ONE_YEAR
"groupLevel": 1,
"reviewNote": "...",
"localizations": {
"en-US": { "name": "Monthly", "description": "Auto-renews monthly" },
"zh-Hans": { "name": "月付", "description": "每月自动续订" }
},
"prices": { "USA": "0.99", "CHN": "8" },
"introOffer": { // 可选;首次订阅奖励
"type": "FREE_TRIAL", // FREE_TRIAL | PAY_AS_YOU_GO | PAY_UP_FRONT
"duration": "ONE_WEEK",
"numberOfPeriods": 1,
"territories": ["USA", "CHN"]
}
}
]
}
],
"appPrice": "free", // App 本体价格(可选,默认 free)
// "free" 或 { "tier": N, "baseTerritory": "USA" }
"ageRating": { // 年龄分级覆盖(可选,默认全 NONE/false → 4+)
// 部分覆盖即可,未指定字段保留默认
// "profanityOrCrudeHumor": "INFREQUENT_MILD",
// "userGeneratedContent": true
},
"testFlight": { // TestFlight 配置(可选)
"internalGroups": ["Internal QA"],
"externalGroups": ["Public Beta"],
"feedbackEmail": "[email protected]",
"betaReviewNotes": "TF-specific notes (optional)",
"locales": {
"en": {
"whatsNew": "Beta-only release notes",
"description": "Beta description shown to testers"
}
}
}
},
// ===== 多语言 =====
"locales": {
"en": {
"name": "SnapPro", // 本地化名称(可选)
"subtitle": "Screenshot & Recording", // 副标题(<= 30 字符)
"keywords": "screenshot,recording", // 关键词(<= 100 字符)
"whatsNew": "- Bug fixes", // 更新说明
"promotionalText": "...", // 推广文案
"fullDescription": "...", // 完整描述
"features": [ // 功能列表(官网用)
{ "title": "...", "description": "..." }
],
"docs": { // 协议文档(官网页面)
"privacy": "...",
"terms": "...",
"support": "...",
"deletion": "..."
}
},
"zh": { ... }
}
}字段用途
| 字段 | ASC | 官网 | 说明 |
|------|-----|------|------|
| id | | yes | 决定 URL 路径 |
| name | fallback | yes | 默认名称 |
| locales.{lang}.name | yes | | 本地化 app 名称 |
| locales.{lang}.subtitle | yes | | 30 字符副标题 |
| locales.{lang}.keywords | yes | | 100 字符关键词 |
| locales.{lang}.whatsNew | yes | | 更新说明 |
| locales.{lang}.fullDescription | yes | yes | 完整描述 |
| locales.{lang}.features | | yes | 功能列表 |
| locales.{lang}.docs.* | | yes | 隐私/条款/支持/删除页面 |
| appStore.copyright | yes | | 版权声明 |
| appStore.reviewContact | yes | | 审核联系人 |
URL 自动生成
以下 URL 由 manifest id 确定性生成,无需手动填写:
supportUrl: https://obelisk.club/apps/{id}/support
marketingUrl: https://obelisk.club/apps/{id}
privacyPolicyUrl: https://obelisk.club/apps/{id}/privacyLocale 映射
manifest 中的 locale key 会自动映射到 ASC 的 locale 格式:
| manifest | ASC |
|----------|-----|
| en | en-US |
| zh | zh-Hans |
| zh-tw | zh-Hant |
| ja | ja |
| ko | ko |
| fr | fr-FR |
| de | de-DE |
GitHub Actions
一键发布
将 templates/appstore-publish.yml 复制到产品仓库的 .github/workflows/,或使用 obelisk init 自动生成。
# 手动触发,输入版本号
on:
workflow_dispatch:
inputs:
version:
description: 'Version (e.g., 1.1.0)'
required: true需要设置的 Secrets:
| Secret | 说明 |
|--------|------|
| APPSTORE_KEY_ID | ASC API Key ID |
| APPSTORE_ISSUER_ID | ASC API Issuer ID |
| APPSTORE_API_KEY_BASE64 | AuthKey .p8 文件的 base64 编码 |
| OBELISK_PAT | GitHub PAT(用于触发 obelisk.club 同步) |
生成 base64:
base64 -i AuthKey_XXXXXX.p8 | pbcopyTestFlight 发布
使用 templates/testflight-publish.yml(obelisk init 自动复制),手动触发即可推送 build 到 TestFlight 内/外测组。
on:
workflow_dispatch:
inputs:
mode: # internal | external
groups: # 可选,覆盖 manifest 的默认组名
build_number: # 可选,默认最新 VALID build
skip_submit: # 仅 external,只加组不提审复用 App Store 发布的同一套 secrets(APPSTORE_KEY_ID / APPSTORE_ISSUER_ID / APPSTORE_API_KEY_BASE64)。
PR 校验
templates/validate-manifest.yml — 当 obelisk/ 目录有改动时自动校验 manifest。
官网同步
obelisk init 生成的 sync-obelisk.yml — push 到 main 或发布 release 时自动同步到 obelisk.club。
截图目录结构
obelisk/screenshots/
app/ # App Store 截图
en/
6.7/ # iPhone 16 Pro Max
6.1/ # iPhone 16
desktop/ # macOS
zh/
6.7/
desktop/
iap/ # IAP 审核截图(静态)
purchase.png
subscription/ # 订阅审核截图(静态)
subscribe.png设备文件夹名对应 ASC displayType:
| 文件夹 | 设备 | ASC displayType |
|--------|------|-----------------|
| 6.9 | iPhone 17 Pro Max | APP_IPHONE_69 |
| 6.7 | iPhone 16 Pro Max | APP_IPHONE_67 |
| 6.1 | iPhone 16 | APP_IPHONE_61 |
| 5.5 | iPhone SE | APP_IPHONE_55 |
| ipad-13 | iPad Pro 13" | APP_IPAD_PRO_3GEN_129 |
| ipad | iPad Pro 11" | APP_IPAD_PRO_129 |
| desktop | macOS | APP_DESKTOP |
与 obelisk.club 的关系
产品仓库
obelisk/manifest.json ← 唯一数据源
|
├── obelisk sync ──→ obelisk.club 官网(隐私/支持/条款页面)
└── obelisk submit ─→ App Store Connect(metadata + 截图 + 提审)manifest.json 同时服务于官网和 ASC:
- 官网
sync-app.mjs只读id、name、themeColor、locales.en、locales.zh - CLI 读全部字段推送到 ASC
- 新增的
appStore、subtitle、keywords等字段对官网透明
不在范围内
以下需要一次性手动设置:
- ASC 首次创建 App(bundle ID、SKU、主语言)
- App Privacy 隐私问卷(首次填写)
- 税务/银行账户设置
- API Key 创建
- 法律协议签署
- 定价配置
- TestFlight beta group 创建(CLI 仅按名称引用,组本身需在 ASC 网页端创建)
项目结构
obelisk-cli/
bin/obelisk.mjs CLI 入口
src/
commands/
init.mjs 交互式初始化
validate.mjs 离线校验
archive.mjs xcodebuild archive → .xcarchive
upload.mjs xcodebuild -exportArchive destination=upload
preflight.mjs codesign + entitlements + privacy manifest
asc/
signing.mjs shared cert/profile/auth/plist helpers
screenshots.mjs 自动截图
sync.mjs 官网同步
submit.mjs ASC 提审
status.mjs 状态查询
publish.mjs 一条龙
asc/
auth.mjs JWT ES256 认证
client.mjs HTTP 客户端(retry + rate limit + dry-run)
builds.mjs Build 查找/等待
versions.mjs 版本管理 + 状态机
localizations.mjs 多语言同步
screenshots.mjs 截图增量上传
review.mjs 审核提交(状态感知)
app-info.mjs 分类/版权/app 名称
iap.mjs IAP/订阅截图
screenshots/
simulator.mjs Simulator 管理
runner.mjs xcodebuild 调度
collector.mjs xcresult 截图提取
manifest/
loader.mjs 加载 + 校验
urls.mjs URL 自动生成
utils/
logger.mjs 结构化日志
checksum.mjs MD5 校验
templates/
appstore-publish.yml App Store 发布 workflow 模板
testflight-publish.yml TestFlight 发布 workflow 模板
validate-manifest.yml 校验 workflow 模板