npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.appIdbundleId 非空
  • 每个 locale 的 keywords <= 100 字符
  • 每个 locale 的 subtitle <= 30 字符
  • reviewContact 字段完整
  • docs.privacydocs.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.xcprivacy

  • App 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.0

obelisk 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.p8

manifest 片段(见下方 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 之后建议:

  1. 打开 banner 给的 Resolution Center 链接读原文
  2. 把新模式追加到 docs/rejection-history.md
  3. 视情况在 src/manifest/guardrails.mjs / src/commands/preflight.mjs 加规则
  4. 后续所有项目自动获得防御

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-02

obelisk 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.0

ASC 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.p8

2. 环境变量

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.p8

3. 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}/privacy

Locale 映射

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 | pbcopy

TestFlight 发布

使用 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 只读 idnamethemeColorlocales.enlocales.zh
  • CLI 读全部字段推送到 ASC
  • 新增的 appStoresubtitlekeywords 等字段对官网透明

不在范围内

以下需要一次性手动设置:

  • 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 模板