@chengyu08/ipa-cli 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,7 @@
17
17
  - 对比两个 IPA 或两个指纹 JSON,输出相似度与风险分类
18
18
  - 基于本地基线库执行 Top N 相似检索
19
19
  - 执行 `analyze + scan + Top1 compare` 的端到端冒烟检查
20
+ - 使用 Apple Transporter 将 IPA 真实上传到 App Store Connect
20
21
  - 生成、校验和批量渲染 App Store 上架图
21
22
  - 批量扫描当前目录或多个子项目中的静态图片风险
22
23
  - 输出机器可消费的静态图片整改清单 JSON
@@ -73,6 +74,13 @@ ipa-cli compare ./AppA.ipa ./AppB.ipa --format panel
73
74
 
74
75
  # 3) 做一次完整冒烟检查
75
76
  ipa-cli smoke ./YourApp.ipa --library ./data/library --format text
77
+
78
+ # 4) 预检并准备上传到 App Store Connect
79
+ ipa-cli upload ./YourApp.ipa \
80
+ --username dev@example.com \
81
+ --password-env APPSTORE_PASSWORD \
82
+ --asset-description ./AppStoreInfo.plist \
83
+ --dry-run
76
84
  ```
77
85
 
78
86
  如果你还没有全局安装,也可以把上面的 `ipa-cli` 替换成 `npx @chengyu08/ipa-cli`。
@@ -180,13 +188,238 @@ ipa-cli smoke ./YourApp.ipa --library ./data/library --format text
180
188
  ipa-cli assets review . --scope children --format panel
181
189
  ```
182
190
 
183
- ### 9. 导出整改清单 JSON
191
+ ### 9. 上传 IPA 到 App Store Connect
192
+
193
+ 这一节给出最常用的上传操作流程。后文 `upload` 命令说明里会再展开每个参数的含义、边界和更多示例。
194
+
195
+ 推荐按下面顺序操作:
196
+
197
+ 1. 先准备好 `.ipa`、账号凭据,以及 Linux / Windows 必需的 `AppStoreInfo.plist`
198
+ 2. 先跑一次 `--dry-run`,确认命令拼装、路径和认证方式都正确
199
+ 3. 再跑一次 `--verify-only`,让 Apple Transporter 先做真实预校验
200
+ 4. 最后执行正式上传
201
+
202
+ 开始前请确认:
203
+
204
+ - App 已经在 App Store Connect 中创建,Bundle ID 与待上传构建一致
205
+ - 账号角色具备上传构建版本权限
206
+ - IPA 是 Xcode 导出的正式构建产物,而不是随手打包的临时文件
207
+ - Linux / Windows 上传时,必须同时提供 Xcode 导出的 `AppStoreInfo.plist`
208
+ - 如果使用 API key,Account Holder 需要先在 App Store Connect 开通 API 访问并生成 key
209
+
210
+ 认证方式建议如下:
211
+
212
+ - 本地手工上传:优先用 `--username + --password-env`
213
+ - CI 或自动化:优先用 `--api-key + --api-issuer + --private-key-file`
214
+ - 已有外部签发 JWT:直接用 `--jwt`
215
+
216
+ ### 9.1 先做 dry-run
217
+
218
+ 先让 CLI 做本地预检,确认它能识别 IPA、认证方式、Transporter 路径和 `AppStoreInfo.plist`,但不会真正调用 Apple 上传。
219
+
220
+ Apple ID 示例:
221
+
222
+ ```bash
223
+ ipa-cli upload ./YourApp.ipa \
224
+ --username dev@example.com \
225
+ --password-env APPSTORE_PASSWORD \
226
+ --asset-description ./AppStoreInfo.plist \
227
+ --dry-run
228
+ ```
229
+
230
+ API key 示例:
231
+
232
+ ```bash
233
+ ipa-cli upload ./YourApp.ipa \
234
+ --api-key ABCD123456 \
235
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a \
236
+ --private-key-file ./AuthKey_ABCD123456.p8 \
237
+ --asset-description ./AppStoreInfo.plist \
238
+ --dry-run
239
+ ```
240
+
241
+ 你会看到一段“上传预检”摘要,包括:
242
+
243
+ - 应用名、Bundle ID、Version、Build
244
+ - 当前使用的 Transporter 模式:`upload` 或 `verify`
245
+ - 当前认证方式:`apple-id`、`api-key` 或 `jwt`
246
+ - `Transporter` 可执行文件路径
247
+ - `Asset Description` 和 `Event File` 路径
248
+
249
+ ### 9.2 再做 Apple 官方预校验
250
+
251
+ 如果你希望在正式上传前就拿到 Apple 侧的校验结果,使用 `--verify-only`。这会调用 Apple Transporter 的 `verify` 模式,不执行真正上传。
252
+
253
+ 只校验 metadata:
254
+
255
+ ```bash
256
+ ipa-cli upload ./YourApp.ipa \
257
+ --verify-only \
258
+ --metadata-only \
259
+ --event-file ./transporter-events.json \
260
+ --api-key ABCD123456 \
261
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a \
262
+ --private-key-file ./AuthKey_ABCD123456.p8 \
263
+ --asset-description ./AppStoreInfo.plist
264
+ ```
265
+
266
+ 校验 metadata 和 assets:
267
+
268
+ ```bash
269
+ ipa-cli upload ./YourApp.ipa \
270
+ --verify-only \
271
+ --event-file ./transporter-events.json \
272
+ --api-key ABCD123456 \
273
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a \
274
+ --private-key-file ./AuthKey_ABCD123456.p8 \
275
+ --asset-description ./AppStoreInfo.plist
276
+ ```
277
+
278
+ 说明:
279
+
280
+ - `--verify-only` 会调用 Apple 官方 `verify` 模式
281
+ - `--metadata-only` 只在 verify 模式下有效
282
+ - `--event-file` 会把 Apple 结构化输出落到文件,便于 CI 归档和后处理
283
+ - verify 成功不代表构建已经上传,只代表它通过了这一步预校验
284
+
285
+ ### 9.3 再执行正式上传
286
+
287
+ Apple ID 上传:
288
+
289
+ ```bash
290
+ ipa-cli upload ./YourApp.ipa \
291
+ --username dev@example.com \
292
+ --password-env APPSTORE_PASSWORD \
293
+ --asset-description ./AppStoreInfo.plist
294
+ ```
295
+
296
+ 如果你的账号关联多个 provider,可以补 `--provider`:
297
+
298
+ ```bash
299
+ ipa-cli upload ./YourApp.ipa \
300
+ --username dev@example.com \
301
+ --password-env APPSTORE_PASSWORD \
302
+ --provider ExampleProvider \
303
+ --asset-description ./AppStoreInfo.plist
304
+ ```
305
+
306
+ API key 上传:
307
+
308
+ ```bash
309
+ ipa-cli upload ./YourApp.ipa \
310
+ --api-key ABCD123456 \
311
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a \
312
+ --private-key-file ./AuthKey_ABCD123456.p8 \
313
+ --asset-description ./AppStoreInfo.plist
314
+ ```
315
+
316
+ 如果你已经有 JWT,也可以直接上传:
317
+
318
+ ```bash
319
+ ipa-cli upload ./YourApp.ipa \
320
+ --jwt "$ASC_JWT" \
321
+ --asset-description ./AppStoreInfo.plist
322
+ ```
323
+
324
+ 上传成功后,CLI 只表示“上传请求已提交给 Apple”。构建通常还要经过 App Store Connect processing,稍后才会在后台可见。
325
+
326
+ ### 9.4 常见场景示例
327
+
328
+ macOS 本地上传,省略 `AppStoreInfo.plist`:
329
+
330
+ ```bash
331
+ ipa-cli upload ./YourApp.ipa \
332
+ --username dev@example.com \
333
+ --password-env APPSTORE_PASSWORD
334
+ ```
335
+
336
+ 说明:macOS 允许不传 `AppStoreInfo.plist`,但如果你本来就有 Xcode 导出的文件,仍然建议一起传入,行为更稳定。
337
+
338
+ Linux 上传:
339
+
340
+ ```bash
341
+ ipa-cli upload ./YourApp.ipa \
342
+ --api-key ABCD123456 \
343
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a \
344
+ --private-key-file ./AuthKey_ABCD123456.p8 \
345
+ --asset-description ./AppStoreInfo.plist
346
+ ```
347
+
348
+ Windows 上传并显式指定 Transporter 路径:
349
+
350
+ ```bash
351
+ ipa-cli upload .\\YourApp.ipa ^
352
+ --transporter "C:\\Program Files (x86)\\itms\\iTMSTransporter.cmd" ^
353
+ --api-key ABCD123456 ^
354
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a ^
355
+ --private-key-file .\\AuthKey_ABCD123456.p8 ^
356
+ --asset-description .\\AppStoreInfo.plist
357
+ ```
358
+
359
+ API key 但不显式传私钥文件:
360
+
361
+ ```bash
362
+ ipa-cli upload ./YourApp.ipa \
363
+ --api-key ABCD123456 \
364
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a \
365
+ --asset-description ./AppStoreInfo.plist
366
+ ```
367
+
368
+ 说明:这种方式依赖 Transporter 自己按默认规则查找 `AuthKey_<keyId>.p8`。如果你希望命令行为更可控,建议始终显式传 `--private-key-file`。
369
+
370
+ CI 示例:
371
+
372
+ ```bash
373
+ export APPSTORE_PASSWORD="xxxx-xxxx-xxxx-xxxx"
374
+
375
+ ipa-cli upload ./build/YourApp.ipa \
376
+ --verify-only \
377
+ --event-file ./artifacts/transporter-events.json \
378
+ --username dev@example.com \
379
+ --password-env APPSTORE_PASSWORD \
380
+ --asset-description ./build/AppStoreInfo.plist
381
+
382
+ ipa-cli upload ./build/YourApp.ipa \
383
+ --username dev@example.com \
384
+ --password-env APPSTORE_PASSWORD \
385
+ --asset-description ./build/AppStoreInfo.plist
386
+ ```
387
+
388
+ ### 9.5 安全建议
389
+
390
+ - 优先使用 `--password-env`,不要把密码直接写死在 shell 历史里
391
+ - 不要把 `.p8` 私钥提交到仓库
392
+ - `--event-file` 中可能包含 Apple 结构化返回信息,建议和构建产物一起归档,但不要对外公开
393
+ - 如果你在 CI 中使用 JWT,尽量设置短有效期,避免长期复用
394
+
395
+ ### 9.6 常见报错与处理
396
+
397
+ - `未找到 Apple Transporter`
398
+ 先安装 Apple Transporter,或者通过 `--transporter` 显式指定 `iTMSTransporter` 路径
399
+ - `Linux / Windows 上传 App 时必须提供 --asset-description`
400
+ 说明当前是非 macOS 环境,必须补上 Xcode 导出的 `AppStoreInfo.plist`
401
+ - `--metadata-only 仅能和 --verify-only 一起使用`
402
+ 说明你把 metadata-only 用在了正式上传模式,去掉它或者同时加上 `--verify-only`
403
+ - `必须提供一种上传认证方式`
404
+ 说明当前没有提供 Apple ID、API key 或 JWT 中的任意一种
405
+ - `上传认证方式只能选择一种`
406
+ 说明同时传了多套认证方式,例如 `--username` 和 `--api-key` 同时出现
407
+ - `IPA 缺少 CFBundleIdentifier / CFBundleShortVersionString / CFBundleVersion`
408
+ 说明当前包不是一个可用于 App Store Connect 上传的标准导出产物,先回到 Xcode 导出环节检查
409
+
410
+ Apple 官方参考:
411
+
412
+ - [Upload builds](https://developer.apple.com/help/app-store-connect/manage-builds/upload-builds/)
413
+ - [Transporter User Guide](https://help.apple.com/itc/transporteruserguide/en.lproj/static.html?from=20423&from_column=20423)
414
+ - [Generating Tokens for API Requests](https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests)
415
+
416
+ ### 10. 导出整改清单 JSON
184
417
 
185
418
  ```bash
186
419
  ipa-cli assets manifest . --scope children --output ./assets-manifest.json
187
420
  ```
188
421
 
189
- ### 10. 定时运行建议
422
+ ### 11. 定时运行建议
190
423
 
191
424
  如果你要长期自动拉取并入库,推荐用本地 `.env` + wrapper script,而不是把凭据直接写进 `launchd`:
192
425
 
@@ -238,6 +471,7 @@ cp ./scripts/ipatool.env.example ./scripts/ipatool.env
238
471
  - `compare`:对比两个 IPA 或两个指纹 JSON
239
472
  - `scan`:把目标样本与本地基线库做 Top N 检索
240
473
  - `smoke`:执行 `analyze + scan + Top1 compare` 的端到端冒烟检查
474
+ - `upload`:使用 Apple Transporter 将 IPA 上传到 App Store Connect
241
475
  - `screenshots init-config`:初始化上架图工作区,生成配置、`raw/` 目录和说明文档
242
476
  - `screenshots from-ipa`:从 IPA 中提取候选图片并自动生成上架图
243
477
  - `screenshots from-simulator`:从 iOS 模拟器采集一张原始截图
@@ -407,6 +641,105 @@ ipa-cli smoke ./fingerprint.json --library ./data/library --format panel
407
641
  ipa-cli smoke ./fingerprint.json --library ./data/library --format html
408
642
  ```
409
643
 
644
+ ### `upload`
645
+
646
+ 使用 Apple Transporter 将 IPA 上传到 App Store Connect,或先调用 Apple 官方 `verify` 模式做真实预校验。
647
+
648
+ ```bash
649
+ ipa-cli upload <ipa> [options]
650
+ ```
651
+
652
+ 最常见的两种用法如下:
653
+
654
+ 先做真实预校验:
655
+
656
+ ```bash
657
+ ipa-cli upload ./YourApp.ipa \
658
+ --verify-only \
659
+ --event-file ./transporter-events.json \
660
+ --api-key ABCD123456 \
661
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a \
662
+ --private-key-file ./AuthKey_ABCD123456.p8 \
663
+ --asset-description ./AppStoreInfo.plist
664
+ ```
665
+
666
+ 再做正式上传:
667
+
668
+ ```bash
669
+ ipa-cli upload ./YourApp.ipa \
670
+ --api-key ABCD123456 \
671
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a \
672
+ --private-key-file ./AuthKey_ABCD123456.p8 \
673
+ --asset-description ./AppStoreInfo.plist
674
+ ```
675
+
676
+ 参数:
677
+
678
+ - `<ipa>`:待上传的 `.ipa` 文件
679
+ - `--verify-only`:调用 Apple Transporter `verify` 模式做真实预校验,不执行上传
680
+ - `--metadata-only`:只在 verify 模式下有效,只校验 metadata,不校验 assets
681
+ - `--transporter <path>`:显式指定 `iTMSTransporter` 路径
682
+ - `--asset-description <path>`:Xcode 导出的 `AppStoreInfo.plist`;Linux / Windows 必填
683
+ - `--event-file <path>`:保存 Apple 结构化事件输出,适合 CI 归档或后处理
684
+ - `--username <appleId>`:Apple ID 登录方式
685
+ - `--password <password>`:App 专用密码;更推荐 `--password-env`
686
+ - `--password-env <env>`:从环境变量读取 App 专用密码
687
+ - `--api-key <keyId>`:App Store Connect API key ID
688
+ - `--api-issuer <issuerId>`:Team API key 对应的 issuer ID
689
+ - `--private-key-file <path>`:`AuthKey_<keyId>.p8` 私钥文件;提供后 CLI 会本地生成 JWT
690
+ - `--jwt <token>`:已生成好的 App Store Connect JWT
691
+ - `--provider <shortName>`:仅 Apple ID 模式使用;多 provider 账号常需要
692
+ - `--verbose`:把 Transporter 日志级别提升到 `eXtreme`
693
+ - `--dry-run`:只做本地预检和命令预演,不执行 Apple 上传或 verify
694
+
695
+ 推荐操作顺序:
696
+
697
+ 1. 先跑 `--dry-run`,确认路径、认证方式和 `AppStoreInfo.plist` 没问题
698
+ 2. 再跑 `--verify-only`,拿到 Apple 官方预校验结果
699
+ 3. 最后执行正式上传
700
+
701
+ 更多示例:
702
+
703
+ Apple ID + 环境变量密码:
704
+
705
+ ```bash
706
+ ipa-cli upload ./YourApp.ipa \
707
+ --username dev@example.com \
708
+ --password-env APPSTORE_PASSWORD \
709
+ --asset-description ./AppStoreInfo.plist
710
+ ```
711
+
712
+ Apple ID + 多 provider:
713
+
714
+ ```bash
715
+ ipa-cli upload ./YourApp.ipa \
716
+ --username dev@example.com \
717
+ --password-env APPSTORE_PASSWORD \
718
+ --provider ExampleProvider \
719
+ --asset-description ./AppStoreInfo.plist
720
+ ```
721
+
722
+ direct JWT:
723
+
724
+ ```bash
725
+ ipa-cli upload ./YourApp.ipa \
726
+ --jwt "$ASC_JWT" \
727
+ --asset-description ./AppStoreInfo.plist
728
+ ```
729
+
730
+ Windows 显式指定 Transporter:
731
+
732
+ ```bash
733
+ ipa-cli upload .\\YourApp.ipa ^
734
+ --transporter "C:\\Program Files (x86)\\itms\\iTMSTransporter.cmd" ^
735
+ --api-key ABCD123456 ^
736
+ --api-issuer 57246542-96fe-1a63-e053-0824d011072a ^
737
+ --private-key-file .\\AuthKey_ABCD123456.p8 ^
738
+ --asset-description .\\AppStoreInfo.plist
739
+ ```
740
+
741
+ 如果你只是想先判断当前包“能不能过 Transporter 这一关”,优先用 `--verify-only`。如果 verify 成功,再执行正式上传,失败定位会更清楚。
742
+
410
743
  ### `screenshots`
411
744
 
412
745
  生成、渲染并校验 App Store 上架图。README 已内置完整用法说明,不需要跳转外部文档也能查看命令、配置字段和目录约定。
package/dist/src/cli.js CHANGED
@@ -12,11 +12,12 @@ import { registerScanBatchCommand } from "./commands/scan-batch.js";
12
12
  import { registerScreenshotsCommand } from "./commands/screenshots.js";
13
13
  import { registerSchemaCommand } from "./commands/schema.js";
14
14
  import { registerSmokeCommand } from "./commands/smoke.js";
15
+ import { registerUploadCommand } from "./commands/upload.js";
15
16
  const program = new Command();
16
17
  program
17
18
  .name("ipa-cli")
18
19
  .description("检查 IPA 的提审风险信号与相似性指标。")
19
- .version("0.2.0")
20
+ .version("0.2.1")
20
21
  .showHelpAfterError();
21
22
  registerAnalyzeCommand(program);
22
23
  registerAssetsCommand(program);
@@ -30,5 +31,6 @@ registerScreenshotsCommand(program);
30
31
  registerSchemaCommand(program);
31
32
  registerSmokeCommand(program);
32
33
  registerBaselineCommand(program);
34
+ registerUploadCommand(program);
33
35
  await program.parseAsync(process.argv);
34
36
  //# sourceMappingURL=cli.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,+BAA+B,EAAE,MAAM,iCAAiC,CAAC;AAClF,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,uBAAuB,CAAC;KACpC,OAAO,CAAC,OAAO,CAAC;KAChB,kBAAkB,EAAE,CAAC;AAExB,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,+BAA+B,CAAC,OAAO,CAAC,CAAC;AACzC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,wBAAwB,CAAC,OAAO,CAAC,CAAC;AAClC,0BAA0B,CAAC,OAAO,CAAC,CAAC;AACpC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAEjC,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,+BAA+B,EAAE,MAAM,iCAAiC,CAAC;AAClF,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,uBAAuB,CAAC;KACpC,OAAO,CAAC,OAAO,CAAC;KAChB,kBAAkB,EAAE,CAAC;AAExB,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,+BAA+B,CAAC,OAAO,CAAC,CAAC;AACzC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,wBAAwB,CAAC,OAAO,CAAC,CAAC;AAClC,0BAA0B,CAAC,OAAO,CAAC,CAAC;AACpC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,uBAAuB,CAAC,OAAO,CAAC,CAAC;AACjC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAE/B,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -20,15 +20,15 @@ export function registerIpCommand(program) {
20
20
  });
21
21
  }
22
22
  function renderIpOutput(publicIp, publicLocation, lanIps, publicError) {
23
- const lines = [publicIp ? `公网 IP: ${publicIp}` : `公网 IP: 获取失败 (${publicError ?? "unknown error"})`, "公网归属地:"];
23
+ const lines = ["公网 IP:"];
24
24
  if (publicLocation) {
25
- lines.push(...renderPublicLocationLines(publicLocation));
25
+ lines.push(renderPublicSummaryLine(publicIp ?? publicLocation.ip, publicLocation));
26
26
  }
27
27
  else if (publicIp) {
28
- lines.push(`- 获取失败: ${publicError ?? "unknown error"}`);
28
+ lines.push(`您的IP地址是:[${publicIp}] 来自:获取失败 (${publicError ?? "unknown error"})`);
29
29
  }
30
30
  else {
31
- lines.push("- 未获取");
31
+ lines.push(`获取失败 (${publicError ?? "unknown error"})`);
32
32
  }
33
33
  lines.push("局域网 IP:");
34
34
  if (lanIps.length === 0) {
@@ -40,59 +40,39 @@ function renderIpOutput(publicIp, publicLocation, lanIps, publicError) {
40
40
  }
41
41
  return lines.join("\n");
42
42
  }
43
- function renderPublicLocationLines(location) {
44
- const lines = [
45
- `- IP 类型: ${location.type ?? "未知"}`
46
- ];
47
- pushDetail(lines, "大洲", formatLabeledValue(location.continent, location.continentCode));
48
- pushDetail(lines, "国家/地区", formatCountryValue(location.country, location.countryCode, location.flagEmoji));
49
- pushDetail(lines, "省/州", formatLabeledValue(location.region, location.regionCode));
50
- pushDetail(lines, "城市", location.city);
51
- pushDetail(lines, "邮编", location.postal);
52
- if (location.latitude !== undefined && location.longitude !== undefined) {
53
- pushDetail(lines, "坐标", `${location.latitude}, ${location.longitude}`);
43
+ function renderPublicSummaryLine(publicIp, location) {
44
+ if (location.displayLocation) {
45
+ return `您的IP地址是:[${publicIp}] 来自:${location.displayLocation}`;
54
46
  }
55
- pushDetail(lines, "时区", formatTimezoneValue(location.timezone));
56
- if (location.timezone.isDst !== undefined) {
57
- pushDetail(lines, "夏令时", location.timezone.isDst ? "是" : "否");
58
- }
59
- pushDetail(lines, "首都", location.capital);
60
- pushDetail(lines, "国际区号", location.callingCode ? `+${location.callingCode}` : undefined);
61
- pushDetail(lines, "边境国家/地区代码", location.borders);
62
- pushDetail(lines, "ASN", location.connection.asn !== undefined ? `AS${location.connection.asn}` : undefined);
63
- pushDetail(lines, "ISP", location.connection.isp);
64
- pushDetail(lines, "组织", location.connection.org);
65
- pushDetail(lines, "域名", location.connection.domain);
66
- return lines;
47
+ const summary = [
48
+ location.country,
49
+ normalizeRegion(location.region, location.country),
50
+ location.city,
51
+ pickNetworkLabel(location)
52
+ ]
53
+ .filter((value) => Boolean(value))
54
+ .filter((value, index, values) => values.indexOf(value) === index);
55
+ return `您的IP地址是:[${publicIp}] 来自:${summary.join(" ") || "未知"}`;
67
56
  }
68
- function pushDetail(lines, label, value) {
69
- if (value) {
70
- lines.push(`- ${label}: ${value}`);
57
+ function normalizeRegion(region, country) {
58
+ if (!region) {
59
+ return undefined;
71
60
  }
72
- }
73
- function formatLabeledValue(label, code) {
74
- if (label && code) {
75
- return `${label} (${code})`;
61
+ if (country && region.startsWith(country)) {
62
+ const trimmedRegion = region.slice(country.length).trim();
63
+ return trimmedRegion || region;
76
64
  }
77
- return label ?? code;
65
+ return region;
78
66
  }
79
- function formatCountryValue(country, countryCode, emoji) {
80
- const base = formatLabeledValue(country, countryCode);
81
- if (!base) {
82
- return undefined;
67
+ function pickNetworkLabel(location) {
68
+ const { isp, org, domain } = location.connection;
69
+ if (isp && !looksLikeStreetAddress(isp)) {
70
+ return isp;
83
71
  }
84
- return emoji ? `${base} ${emoji}` : base;
72
+ return org ?? domain;
85
73
  }
86
- function formatTimezoneValue(timezone) {
87
- const parts = [timezone.id, timezone.abbr].filter(Boolean);
88
- const base = parts.join(" ");
89
- if (base && timezone.utc) {
90
- return `${base} (UTC${timezone.utc})`;
91
- }
92
- if (base) {
93
- return base;
94
- }
95
- return timezone.utc ? `UTC${timezone.utc}` : undefined;
74
+ function looksLikeStreetAddress(value) {
75
+ return /(?:^|\b)(?:no\.?\s*\d+|street|road|rd\b|avenue|ave\b|boulevard|blvd\b|lane|ln\b|drive|dr\b)/i.test(value);
96
76
  }
97
77
  function formatError(error) {
98
78
  if (error instanceof Error) {
@@ -1 +1 @@
1
- {"version":3,"file":"ip.js","sourceRoot":"","sources":["../../../src/commands/ip.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ip.js","sourceRoot":"","sources":["../../../src/commands/ip.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerUploadCommand(program: Command): void;
@@ -0,0 +1,74 @@
1
+ import { prepareIpaUpload, renderUploadCommand, runPreparedIpaUpload } from "../core/upload.js";
2
+ import { withProgress } from "../lib/progress.js";
3
+ export function registerUploadCommand(program) {
4
+ program
5
+ .command("upload")
6
+ .description("使用 Apple Transporter 将 IPA 上传到 App Store Connect。")
7
+ .argument("<ipa>", "待上传的 .ipa 文件")
8
+ .option("--verify-only", "调用 Apple Transporter verify 模式做真实预校验,不执行上传", false)
9
+ .option("--metadata-only", "verify 模式下只校验 metadata,不校验 assets", false)
10
+ .option("--transporter <path>", "iTMSTransporter 可执行文件路径")
11
+ .option("--asset-description <path>", "Xcode 导出的 AppStoreInfo.plist;Linux / Windows 必填")
12
+ .option("--event-file <path>", "保存 Apple 结构化事件输出,便于 CI 或其他工具解析")
13
+ .option("--username <appleId>", "App Store Connect 登录 Apple ID")
14
+ .option("--password <password>", "App 专用密码")
15
+ .option("--password-env <env>", "从环境变量读取 App 专用密码")
16
+ .option("--api-key <keyId>", "App Store Connect API key ID")
17
+ .option("--api-issuer <issuerId>", "Team API key 对应的 issuer ID")
18
+ .option("--private-key-file <path>", "AuthKey_<keyId>.p8 私钥文件;提供后会本地生成 JWT")
19
+ .option("--jwt <token>", "预先生成好的 App Store Connect JWT")
20
+ .option("--provider <shortName>", "可选 Provider short name;多 provider 账号通常需要")
21
+ .option("--verbose", "将 Transporter 日志级别提升到 eXtreme", false)
22
+ .option("--dry-run", "只做预检并输出命令,不实际上传", false)
23
+ .action(async (ipa, options) => {
24
+ const prepared = await withProgress("正在校验 IPA 与上传参数", !(options.dryRun ?? false), async () => {
25
+ return await prepareIpaUpload({
26
+ ipaPath: ipa,
27
+ mode: options.verifyOnly ? "verify" : "upload",
28
+ transporterPath: options.transporter,
29
+ assetDescriptionPath: options.assetDescription,
30
+ eventFilePath: options.eventFile,
31
+ username: options.username,
32
+ password: options.password,
33
+ passwordEnv: options.passwordEnv,
34
+ apiKey: options.apiKey,
35
+ apiIssuer: options.apiIssuer,
36
+ privateKeyFile: options.privateKeyFile,
37
+ jwt: options.jwt,
38
+ provider: options.provider,
39
+ metadataOnly: options.metadataOnly,
40
+ verbose: options.verbose
41
+ });
42
+ });
43
+ process.stdout.write(renderPreparedUploadSummary(prepared));
44
+ if (prepared.warnings.length > 0) {
45
+ process.stdout.write(`注意事项:\n${prepared.warnings.map((warning) => `- ${warning}`).join("\n")}\n`);
46
+ }
47
+ if (options.dryRun ?? false) {
48
+ process.stdout.write("Dry run: 以下命令已通过预检,但不会实际上传。\n");
49
+ process.stdout.write(`${renderUploadCommand(prepared)}\n`);
50
+ return;
51
+ }
52
+ process.stdout.write(prepared.mode === "verify" ? "开始执行 Apple Transporter verify 预校验...\n" : "开始上传到 App Store Connect...\n");
53
+ await runPreparedIpaUpload(prepared);
54
+ process.stdout.write(prepared.mode === "verify"
55
+ ? "Apple Transporter verify 已完成。若未报错,说明当前包已通过 Apple 侧基础预校验。\n"
56
+ : "上传请求已提交给 Apple。构建包仍需经过 App Store Connect processing,稍后才会出现在后台。\n");
57
+ });
58
+ }
59
+ function renderPreparedUploadSummary(prepared) {
60
+ return [
61
+ "上传预检:",
62
+ `- 应用: ${prepared.app.displayName}`,
63
+ `- Bundle ID: ${prepared.app.bundleId}`,
64
+ `- Version: ${prepared.app.version}`,
65
+ `- Build: ${prepared.app.build}`,
66
+ `- 最低系统: ${prepared.app.minIosVersion}`,
67
+ `- Transporter 模式: ${prepared.mode}`,
68
+ `- 认证方式: ${prepared.authMode}`,
69
+ `- Transporter: ${prepared.transporterPath}`,
70
+ `- Asset Description: ${prepared.assetDescriptionPath ?? "未提供"}`,
71
+ `- Event File: ${prepared.eventFilePath ?? "未提供"}`
72
+ ].join("\n") + "\n";
73
+ }
74
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/commands/upload.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,7 @@ export interface LanIpEntry {
20
20
  }
21
21
  export interface PublicIpLocation {
22
22
  ip: string;
23
+ displayLocation?: string;
23
24
  type?: "IPv4" | "IPv6";
24
25
  continent?: string;
25
26
  continentCode?: string;