@chengyu08/ipa-cli 0.1.11 → 0.2.0

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.
Files changed (80) hide show
  1. package/README.md +673 -12
  2. package/README.screenshots.md +578 -0
  3. package/assets/screenshot-frames/iphone-dark-01.png +0 -0
  4. package/assets/screenshot-frames/iphone-dark-02.png +0 -0
  5. package/dist/src/cli.js +5 -1
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/commands/analyze.d.ts +44 -1
  8. package/dist/src/commands/doctor.d.ts +2 -0
  9. package/dist/src/commands/doctor.js +28 -0
  10. package/dist/src/commands/doctor.js.map +1 -0
  11. package/dist/src/commands/ip.d.ts +2 -0
  12. package/dist/src/commands/ip.js +103 -0
  13. package/dist/src/commands/ip.js.map +1 -0
  14. package/dist/src/commands/scan-batch.js +55 -1
  15. package/dist/src/commands/scan-batch.js.map +1 -1
  16. package/dist/src/core/doctor.d.ts +40 -0
  17. package/dist/src/core/doctor.js +172 -0
  18. package/dist/src/core/doctor.js.map +1 -0
  19. package/dist/src/core/extract/dsym.d.ts +7 -0
  20. package/dist/src/core/extract/dsym.js +96 -0
  21. package/dist/src/core/extract/dsym.js.map +1 -0
  22. package/dist/src/core/extract/flutter-manifest.d.ts +29 -0
  23. package/dist/src/core/extract/flutter-manifest.js +346 -0
  24. package/dist/src/core/extract/flutter-manifest.js.map +1 -0
  25. package/dist/src/core/extract/frameworks.js +7 -2
  26. package/dist/src/core/extract/frameworks.js.map +1 -1
  27. package/dist/src/core/extract/resources.d.ts +18 -0
  28. package/dist/src/core/extract/resources.js +76 -5
  29. package/dist/src/core/extract/resources.js.map +1 -1
  30. package/dist/src/core/extract/strings.d.ts +9 -0
  31. package/dist/src/core/extract/strings.js +50 -0
  32. package/dist/src/core/extract/strings.js.map +1 -1
  33. package/dist/src/core/fingerprint.js +173 -8
  34. package/dist/src/core/fingerprint.js.map +1 -1
  35. package/dist/src/core/flutter-assets.d.ts +8 -0
  36. package/dist/src/core/flutter-assets.js +59 -0
  37. package/dist/src/core/flutter-assets.js.map +1 -0
  38. package/dist/src/core/ip.d.ts +55 -0
  39. package/dist/src/core/ip.js +249 -0
  40. package/dist/src/core/ip.js.map +1 -0
  41. package/dist/src/core/library/batch-scan.js +44 -2
  42. package/dist/src/core/library/batch-scan.js.map +1 -1
  43. package/dist/src/core/library/normalize-import.js +43 -0
  44. package/dist/src/core/library/normalize-import.js.map +1 -1
  45. package/dist/src/core/library/scan.d.ts +26 -0
  46. package/dist/src/core/library/scan.js +28 -0
  47. package/dist/src/core/library/scan.js.map +1 -1
  48. package/dist/src/core/reporting/explanations.js +60 -2
  49. package/dist/src/core/reporting/explanations.js.map +1 -1
  50. package/dist/src/core/reporting/html.js +100 -1
  51. package/dist/src/core/reporting/html.js.map +1 -1
  52. package/dist/src/core/reporting/markdown.js +66 -2
  53. package/dist/src/core/reporting/markdown.js.map +1 -1
  54. package/dist/src/core/reporting/panel.js +64 -0
  55. package/dist/src/core/reporting/panel.js.map +1 -1
  56. package/dist/src/core/reporting/text.js +32 -0
  57. package/dist/src/core/reporting/text.js.map +1 -1
  58. package/dist/src/core/schema-dictionary.js +85 -0
  59. package/dist/src/core/schema-dictionary.js.map +1 -1
  60. package/dist/src/core/scoring/compare.js +248 -8
  61. package/dist/src/core/scoring/compare.js.map +1 -1
  62. package/dist/src/core/screenshots/config-template.js +106 -3
  63. package/dist/src/core/screenshots/config-template.js.map +1 -1
  64. package/dist/src/core/screenshots/render.js +253 -13
  65. package/dist/src/core/screenshots/render.js.map +1 -1
  66. package/dist/src/models/batch-scan.d.ts +118 -0
  67. package/dist/src/models/batch-scan.js +41 -2
  68. package/dist/src/models/batch-scan.js.map +1 -1
  69. package/dist/src/models/fingerprint.d.ts +44 -1
  70. package/dist/src/models/fingerprint.js +129 -0
  71. package/dist/src/models/fingerprint.js.map +1 -1
  72. package/dist/src/models/library.d.ts +88 -2
  73. package/dist/src/models/report.d.ts +45 -0
  74. package/dist/src/models/report.js +56 -1
  75. package/dist/src/models/report.js.map +1 -1
  76. package/dist/src/models/screenshots.d.ts +121 -0
  77. package/dist/src/models/screenshots.js +29 -2
  78. package/dist/src/models/screenshots.js.map +1 -1
  79. package/docs/screenshots-command.md +106 -4
  80. package/package.json +5 -3
package/README.md CHANGED
@@ -29,6 +29,7 @@
29
29
  说明:
30
30
 
31
31
  - macOS 上如果系统提供 `codesign`、`otool`、`nm`、`xcrun assetutil`,分析结果会更完整
32
+ - 可运行 `ipa-cli doctor` 检查这些增强工具是否可用;缺失时按输出中的安装建议处理
32
33
  - Linux / Windows 上缺少 Apple 专有工具时,会自动降级部分能力,但 CLI 仍可继续运行
33
34
 
34
35
  ## 安装方式
@@ -232,6 +233,7 @@ cp ./scripts/ipatool.env.example ./scripts/ipatool.env
232
233
 
233
234
  当前 CLI 提供以下命令:
234
235
 
236
+ - `doctor`:检查本机 macOS 增强分析工具是否可用
235
237
  - `analyze`:分析单个 IPA 或指纹 JSON
236
238
  - `compare`:对比两个 IPA 或两个指纹 JSON
237
239
  - `scan`:把目标样本与本地基线库做 Top N 检索
@@ -255,6 +257,47 @@ cp ./scripts/ipatool.env.example ./scripts/ipatool.env
255
257
 
256
258
  ## 命令说明
257
259
 
260
+ ### `doctor`
261
+
262
+ 检查本机是否具备 macOS 增强分析工具。
263
+
264
+ ```bash
265
+ ipa-cli doctor [--format panel|json] [--strict]
266
+ ```
267
+
268
+ 参数:
269
+
270
+ - `--format`:输出格式,默认 `panel`
271
+ - `--strict`:缺少任一增强工具时返回非零退出码,适合 CI 或发布前自检
272
+
273
+ 检查项:
274
+
275
+ - `codesign`:读取签名信息与 entitlements
276
+ - `otool`:读取 Mach-O 导入符号和二进制结构信号
277
+ - `nm`:读取符号表,补充 Objective-C/Swift 符号信号
278
+ - `xcrun assetutil`:解析 `Assets.car`,补充高级资源摘要
279
+
280
+ 示例:
281
+
282
+ ```bash
283
+ ipa-cli doctor
284
+ ipa-cli doctor --format json
285
+ ipa-cli doctor --strict
286
+ ```
287
+
288
+ 如果有工具缺失,优先安装或修复 Xcode Command Line Tools:
289
+
290
+ ```bash
291
+ xcode-select --install
292
+ ```
293
+
294
+ 如果仅 `xcrun assetutil` 缺失,通常需要安装完整 Xcode,并确认 Developer 目录指向 Xcode:
295
+
296
+ ```bash
297
+ sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
298
+ sudo xcodebuild -license accept
299
+ ```
300
+
258
301
  ### `analyze`
259
302
 
260
303
  分析单个 IPA 或单个指纹 JSON,并输出标准化结果。
@@ -275,6 +318,12 @@ ipa-cli analyze ./YourApp.ipa --format text
275
318
  ipa-cli analyze ./fingerprint.json --format panel
276
319
  ```
277
320
 
321
+ Flutter 样本额外关注:
322
+
323
+ - `resources.summary.flutter`:`flutter_assets` 规模、生成文件、Debug 三件套、AssetManifest / FontManifest / NativeAssetsManifest 标记
324
+ - `resources.summary.flutter.appBinary*`:`Frameworks/App.framework/App` 的 AOT 摘要,包括体积、SHA-256、UUID、架构、字符串 token、runtime hint
325
+ - 人类可读输出里会显示 `Flutter 资源` 摘要;如果样本包含 `App.framework/App`,还会显示 `AOT` 大小
326
+
278
327
  ### `compare`
279
328
 
280
329
  对比两个 IPA 或两个指纹 JSON,输出相似度报告和风险分类。
@@ -296,6 +345,16 @@ ipa-cli compare ./AppA.ipa ./AppB.ipa --format text
296
345
  ipa-cli compare ./a.json ./b.json --format panel
297
346
  ```
298
347
 
348
+ Flutter 对比额外输出:
349
+
350
+ - `metrics.flutter.appAotSimilarity`:`App.framework/App` 的 AOT 相似度
351
+ - `metrics.flutter.appAotStringSimilarity`:AOT 字符串 token 相似度
352
+ - `metrics.flutter.assetManifestSimilarity` / `fontManifestSimilarity` / `nativeAssetsSimilarity`
353
+ - `metrics.flutter.leftManifestSource` / `rightManifestSource`:`bin|json|none`
354
+ - `metrics.flutter.leftBuildState` / `rightBuildState`:`debug|release-like|mixed|unknown`
355
+
356
+ 人类可读格式会把这些信息显示为 `Flutter专项` 区块,便于区分“公共 Flutter 运行时相似”与“业务 AOT / 资源相似”。
357
+
299
358
  ### `scan`
300
359
 
301
360
  把目标样本与本地基线库做 Top N 检索。
@@ -316,6 +375,15 @@ ipa-cli scan <input> [--library <dir>] [--top <count>]
316
375
  ipa-cli scan ./YourApp.ipa --library ./data/library --top 5
317
376
  ```
318
377
 
378
+ 如果目标或命中样本是 Flutter,Top1 explain 会额外展示:
379
+
380
+ - `App AOT`
381
+ - `AOT字符串`
382
+ - `Flutter业务资源`
383
+ - `AssetManifest`
384
+ - `FontManifest`
385
+ - `Native Assets`
386
+
319
387
  ### `smoke`
320
388
 
321
389
  执行端到端冒烟流程。
@@ -523,7 +591,10 @@ ipa-cli screenshots render ./screenshot.config.json --output ./screenshots --ove
523
591
  "subtitleColor": "#4B5563",
524
592
  "fontFamily": "Inter, Arial, sans-serif",
525
593
  "orientation": "portrait",
526
- "deviceWidthRatio": 0.76
594
+ "deviceWidthRatio": 0.76,
595
+ "deviceFrame": {
596
+ "preset": "iphone-dark-01"
597
+ }
527
598
  },
528
599
  "targets": ["iphone-6.5"],
529
600
  "locales": ["zh-Hans"],
@@ -550,9 +621,12 @@ ipa-cli screenshots render ./screenshot.config.json --output ./screenshots --ove
550
621
  | `defaults.background` | 否 | 全局背景配置;兼容纯色字符串,也支持 `preset`、`solid`、`linear-gradient`、`image`、`raw-blur`、`stack` |
551
622
  | `defaults.titleColor` | 否 | 标题颜色,默认 `#111827` |
552
623
  | `defaults.subtitleColor` | 否 | 副标题颜色,默认 `#4B5563` |
624
+ | `defaults.titleFontSize` | 否 | 标题字号,单位 px;不填时按画布宽度自动计算 |
625
+ | `defaults.subtitleFontSize` | 否 | 副标题字号,单位 px;不填时按画布宽度自动计算 |
553
626
  | `defaults.fontFamily` | 否 | SVG 文本字体族,默认 `Inter, Arial, sans-serif` |
554
627
  | `defaults.orientation` | 否 | `portrait` 或 `landscape`,默认 `portrait` |
555
628
  | `defaults.deviceWidthRatio` | 否 | 设备截图宽度相对画布宽度的比例,范围 `0.35` 到 `0.95`,默认 `0.76` |
629
+ | `defaults.deviceFrame` | 否 | 全局默认手机框配置;支持内置 `preset` 或自定义 `src` + `screen` |
556
630
  | `targets` | 是 | 要生成的设备槽位列表,例如 `["iphone-6.9", "ipad-13"]` |
557
631
  | `locales` | 是 | 要生成的语言列表,例如 `["zh-Hans", "en-US"]` |
558
632
  | `frames[].name` | 是 | 输出文件名;如果不以 `.png` 结尾,会自动补 `.png` |
@@ -565,26 +639,613 @@ ipa-cli screenshots render ./screenshot.config.json --output ./screenshots --ove
565
639
  | `frames[].background` | 否 | 覆盖全局背景配置 |
566
640
  | `frames[].titleColor` | 否 | 覆盖全局标题颜色 |
567
641
  | `frames[].subtitleColor` | 否 | 覆盖全局副标题颜色 |
642
+ | `frames[].titleFontSize` | 否 | 覆盖单张图标题字号,单位 px |
643
+ | `frames[].subtitleFontSize` | 否 | 覆盖单张图副标题字号,单位 px |
644
+ | `frames[].deviceFrame` | 否 | 单张图覆盖手机框配置;可只写 `offsetY`、`scale` 等局部字段 |
568
645
  | `frames[].target` | 否 | 只对某个目标槽位生效 |
569
646
  | `frames[].locale` | 否 | 只对某个语言生效 |
570
647
 
648
+ 字号字段速查:
649
+
650
+ - `defaults.titleFontSize`:全局默认标题字号
651
+ - `defaults.subtitleFontSize`:全局默认副标题字号
652
+ - `frames[].titleFontSize`:覆盖单张图标题字号
653
+ - `frames[].subtitleFontSize`:覆盖单张图副标题字号
654
+ - 单位都是 `px`,值必须是正数
655
+ - `frames[]` 里的字号设置优先级高于 `defaults`
656
+ - `subtitle` 只有在 `layout: "device-with-title-subtitle"` 时显示,所以 `subtitleFontSize` 也只会在这个布局下生效
657
+ - 如果这些字段都不填,渲染器会按画布宽度自动计算字号
658
+
659
+ ```json
660
+ {
661
+ "defaults": {
662
+ "titleFontSize": 72,
663
+ "subtitleFontSize": 38
664
+ },
665
+ "frames": [
666
+ {
667
+ "name": "01_home",
668
+ "raw": "raw/{locale}/01_home.png",
669
+ "layout": "device-with-title-subtitle",
670
+ "title": "快速完成估价",
671
+ "subtitle": "关键流程清晰可见",
672
+ "titleFontSize": 84,
673
+ "subtitleFontSize": 44
674
+ }
675
+ ]
676
+ }
677
+ ```
678
+
571
679
  布局类型:
572
680
 
573
681
  | layout | 画面效果 | 适合场景 | 注意事项 |
574
682
  | --- | --- | --- | --- |
575
683
  | `raw-fullscreen` | 原图直接铺满整个输出画布,不显示设备外框、标题或副标题 | 已经是完整营销图、KV 图、海报风页面图 | `rawFit: "cover"` 时只输出原图;`rawFit: "contain"` 时才会露出背景层 |
576
- | `device-with-title` | 顶部显示一段标题,下方展示带圆角和阴影的设备截图 | 单卖点页、功能页、流程页 | 即使传了 `subtitle`,当前也不会显示 |
577
- | `device-with-title-subtitle` | 顶部显示标题和副标题,下方展示带圆角和阴影的设备截图 | 首页、详情页、需要两级文案的场景 | 这是默认布局;不填 `subtitle` 时会只显示标题区 |
684
+ | `device-with-title` | 顶部显示一段标题,下方展示带手机框的设备截图 | 单卖点页、功能页、流程页 | 即使传了 `subtitle`,当前也不会显示 |
685
+ | `device-with-title-subtitle` | 顶部显示标题和副标题,下方展示带手机框的设备截图 | 首页、详情页、需要两级文案的场景 | 这是默认布局;不填 `subtitle` 时会只显示标题区 |
686
+
687
+ 配置写法示例:
688
+
689
+ ##### 手机框展示
690
+
691
+ 默认配置会启用内置 `iphone-dark-01` 手机框。原始截图会进入手机屏幕区域,按屏幕圆角裁剪,再叠加手机外框。
692
+
693
+ ```json
694
+ {
695
+ "defaults": {
696
+ "deviceFrame": {
697
+ "preset": "iphone-dark-01"
698
+ }
699
+ },
700
+ "frames": [
701
+ {
702
+ "name": "01_home",
703
+ "raw": "raw/{locale}/01_home.png",
704
+ "title": "快速完成估价",
705
+ "deviceFrame": {
706
+ "offsetY": -80,
707
+ "scale": 0.96
708
+ }
709
+ }
710
+ ]
711
+ }
712
+ ```
713
+
714
+ 内置 `preset` 当前支持 `iphone-dark-01` 和 `iphone-dark-02`。如果使用自定义手机框,需要提供 `src` 和以手机框原图像素为准的 `screen` 开窗区域。
715
+
716
+ ##### 全局纯色背景
717
+
718
+ 如果所有截图都使用同一个纯色背景,可以只配置 `defaults.background`。纯色字符串是最短写法,适合浅灰、白色或品牌主色背景。
719
+
720
+ ```json
721
+ {
722
+ "version": 1,
723
+ "platform": "ios",
724
+ "defaults": {
725
+ "background": "#F5F7FA",
726
+ "titleColor": "#111827",
727
+ "subtitleColor": "#4B5563"
728
+ },
729
+ "targets": ["iphone-6.5"],
730
+ "locales": ["zh-Hans"],
731
+ "frames": [
732
+ {
733
+ "name": "01_home",
734
+ "raw": "raw/{locale}/01_home.png",
735
+ "title": "快速完成估价",
736
+ "subtitle": "关键流程清晰可见"
737
+ }
738
+ ]
739
+ }
740
+ ```
741
+
742
+ 如果希望结构更统一,也可以把纯色写成对象:
743
+
744
+ ```json
745
+ {
746
+ "defaults": {
747
+ "background": {
748
+ "type": "solid",
749
+ "color": "#0F172A"
750
+ },
751
+ "titleColor": "#FFFFFF",
752
+ "subtitleColor": "#CBD5E1"
753
+ }
754
+ }
755
+ ```
756
+
757
+ ##### 全局渐变背景
758
+
759
+ `linear-gradient` 适合给整套上架图统一品牌氛围。`angle` 表示渐变角度,`stops` 按 `offset` 从 `0` 到 `1` 配置颜色节点。
760
+
761
+ ```json
762
+ {
763
+ "version": 1,
764
+ "platform": "ios",
765
+ "defaults": {
766
+ "background": {
767
+ "type": "linear-gradient",
768
+ "angle": 180,
769
+ "stops": [
770
+ { "offset": 0, "color": "#EEF2FF" },
771
+ { "offset": 0.52, "color": "#DBEAFE" },
772
+ { "offset": 1, "color": "#F8FAFC" }
773
+ ]
774
+ },
775
+ "titleColor": "#172554",
776
+ "subtitleColor": "#475569",
777
+ "orientation": "portrait",
778
+ "deviceWidthRatio": 0.76
779
+ },
780
+ "targets": ["iphone-6.5"],
781
+ "locales": ["zh-Hans"],
782
+ "frames": [
783
+ {
784
+ "name": "01_home",
785
+ "raw": "raw/{locale}/01_home.png",
786
+ "title": "快速完成估价",
787
+ "subtitle": "核心流程一眼看清",
788
+ "layout": "device-with-title-subtitle"
789
+ },
790
+ {
791
+ "name": "02_detail",
792
+ "raw": "raw/{locale}/02_detail.png",
793
+ "title": "详情信息完整展示",
794
+ "subtitle": "关键数据层级清晰",
795
+ "layout": "device-with-title-subtitle"
796
+ }
797
+ ]
798
+ }
799
+ ```
800
+
801
+ 单张图也可以覆盖全局渐变。下面示例中,`01_home` 使用蓝紫渐变,`02_detail` 使用默认背景。
802
+
803
+ ```json
804
+ {
805
+ "defaults": {
806
+ "background": "#F8FAFC"
807
+ },
808
+ "targets": ["iphone-6.5"],
809
+ "locales": ["zh-Hans"],
810
+ "frames": [
811
+ {
812
+ "name": "01_home",
813
+ "raw": "raw/{locale}/01_home.png",
814
+ "title": "快速完成估价",
815
+ "background": {
816
+ "type": "linear-gradient",
817
+ "angle": 145,
818
+ "stops": [
819
+ { "offset": 0, "color": "#312E81" },
820
+ { "offset": 0.55, "color": "#2563EB" },
821
+ { "offset": 1, "color": "#38BDF8" }
822
+ ]
823
+ },
824
+ "titleColor": "#FFFFFF",
825
+ "subtitleColor": "#DBEAFE"
826
+ },
827
+ {
828
+ "name": "02_detail",
829
+ "raw": "raw/{locale}/02_detail.png",
830
+ "title": "详情信息完整展示"
831
+ }
832
+ ]
833
+ }
834
+ ```
835
+
836
+ ##### 图片背景
837
+
838
+ `image` 背景适合使用品牌纹理、活动海报、插画或运营底图。`src` 相对 `screenshot.config.json` 所在目录解析,不是相对当前 shell 目录。
839
+
840
+ 推荐目录结构:
841
+
842
+ ```text
843
+ screenshots-workspace/
844
+ screenshot.config.json
845
+ backgrounds/
846
+ hero.png
847
+ zh-Hans/
848
+ iphone-6.5/
849
+ 01_home.png
850
+ raw/
851
+ zh-Hans/
852
+ 01_home.png
853
+ ```
854
+
855
+ 固定使用一张背景图时,可以这样配置:
856
+
857
+ ```json
858
+ {
859
+ "defaults": {
860
+ "background": {
861
+ "type": "image",
862
+ "src": "backgrounds/hero.png",
863
+ "fit": "cover",
864
+ "position": "center",
865
+ "overlay": {
866
+ "color": "#020617",
867
+ "opacity": 0.18
868
+ }
869
+ },
870
+ "titleColor": "#FFFFFF",
871
+ "subtitleColor": "#E2E8F0"
872
+ },
873
+ "targets": ["iphone-6.5"],
874
+ "locales": ["zh-Hans"],
875
+ "frames": [
876
+ {
877
+ "name": "01_home",
878
+ "raw": "raw/{locale}/01_home.png",
879
+ "title": "首页体验全面升级",
880
+ "subtitle": "核心入口更清晰"
881
+ }
882
+ ]
883
+ }
884
+ ```
578
885
 
579
- 背景配置可以写在 `defaults.background`、`defaults.backgroundPresets.<name>` `frames[].background`。`frames[].background` 优先级最高;如果只想引用预设,也可以使用 `frames[].backgroundPreset`。当前支持的背景类型包括:
886
+ 如果每个语言、设备或页面都需要不同背景图,可以在 `src` 里使用 `{locale}`、`{target}`、`{frame}` 占位符。`{frame}` 使用当前 `frames[].name`。
580
887
 
581
- - 纯色字符串:`"#F5F7FA"`
582
- - `solid`:显式声明纯色背景,例如 `{ "type": "solid", "color": "#0F172A" }`
583
- - `linear-gradient`:线性渐变背景,适合品牌色过渡和浅色氛围底
584
- - `image`:把本地图片铺成背景,`src` 相对配置文件目录解析,支持 `{locale}`、`{target}`、`{frame}` 占位符
585
- - `raw-blur`:把当前 frame 的 `raw` 原图拉伸、模糊后当作背景
586
- - `stack`:按顺序叠加多层背景,每层可写 `opacity` 和 `blend`
587
- - `preset`:引用 `defaults.backgroundPresets` 里定义的背景预设
888
+ ```json
889
+ {
890
+ "defaults": {
891
+ "background": {
892
+ "type": "image",
893
+ "src": "backgrounds/{locale}/{target}/{frame}.png",
894
+ "fit": "cover",
895
+ "position": "center",
896
+ "overlay": {
897
+ "color": "#111827",
898
+ "opacity": 0.2
899
+ }
900
+ },
901
+ "titleColor": "#FFFFFF",
902
+ "subtitleColor": "#F8FAFC"
903
+ },
904
+ "targets": ["iphone-6.9", "iphone-6.5"],
905
+ "locales": ["zh-Hans", "en-US"],
906
+ "frames": [
907
+ {
908
+ "name": "01_home",
909
+ "raw": "raw/{locale}/01_home.png",
910
+ "title": "快速完成估价",
911
+ "subtitle": "关键流程清晰可见"
912
+ }
913
+ ]
914
+ }
915
+ ```
916
+
917
+ ##### 原图模糊背景
918
+
919
+ `raw-blur` 会把当前 frame 的 `raw` 原图拉伸、模糊后作为背景。它适合没有额外设计素材,但希望背景色调和截图内容自然一致的场景。
920
+
921
+ ```json
922
+ {
923
+ "defaults": {
924
+ "background": {
925
+ "type": "raw-blur",
926
+ "blur": 28,
927
+ "overlay": {
928
+ "color": "#020617",
929
+ "opacity": 0.22
930
+ }
931
+ },
932
+ "titleColor": "#FFFFFF",
933
+ "subtitleColor": "#E5E7EB"
934
+ },
935
+ "targets": ["iphone-6.5"],
936
+ "locales": ["zh-Hans"],
937
+ "frames": [
938
+ {
939
+ "name": "01_home",
940
+ "raw": "raw/{locale}/01_home.png",
941
+ "title": "首页信息一眼看清",
942
+ "subtitle": "背景自动取自当前页面截图"
943
+ }
944
+ ]
945
+ }
946
+ ```
947
+
948
+ ##### 多层背景 stack
949
+
950
+ `stack` 会从下到上叠加背景层。它适合把渐变、图片纹理、遮罩组合在一起,做出更稳定的品牌视觉。
951
+
952
+ ```json
953
+ {
954
+ "defaults": {
955
+ "background": {
956
+ "type": "stack",
957
+ "layers": [
958
+ {
959
+ "type": "linear-gradient",
960
+ "angle": 180,
961
+ "stops": [
962
+ { "offset": 0, "color": "#EFF6FF" },
963
+ { "offset": 1, "color": "#DBEAFE" }
964
+ ]
965
+ },
966
+ {
967
+ "type": "image",
968
+ "src": "backgrounds/noise.png",
969
+ "fit": "cover",
970
+ "opacity": 0.12,
971
+ "blend": "overlay"
972
+ },
973
+ {
974
+ "type": "solid",
975
+ "color": "#FFFFFF",
976
+ "opacity": 0.08
977
+ }
978
+ ]
979
+ },
980
+ "titleColor": "#0F172A",
981
+ "subtitleColor": "#475569"
982
+ },
983
+ "targets": ["iphone-6.5"],
984
+ "locales": ["zh-Hans"],
985
+ "frames": [
986
+ {
987
+ "name": "01_home",
988
+ "raw": "raw/{locale}/01_home.png",
989
+ "title": "首页信息一眼看清",
990
+ "subtitle": "柔和渐变叠加纹理背景"
991
+ }
992
+ ]
993
+ }
994
+ ```
995
+
996
+ `blend` 当前支持 `over`、`multiply`、`screen`、`overlay`、`darken`、`lighten`、`soft-light`、`hard-light`。如果只是轻微叠加纹理,通常使用 `overlay` 并把 `opacity` 控制在 `0.08` 到 `0.18` 之间。
997
+
998
+ ##### 背景预设 preset
999
+
1000
+ 如果多张图需要复用同一套背景,不建议在每个 frame 里复制长配置。可以先在 `defaults.backgroundPresets` 定义预设,再通过 `defaults.backgroundPreset` 或 `frames[].backgroundPreset` 引用。
1001
+
1002
+ ```json
1003
+ {
1004
+ "version": 1,
1005
+ "platform": "ios",
1006
+ "defaults": {
1007
+ "backgroundPresets": {
1008
+ "blue-soft": {
1009
+ "type": "linear-gradient",
1010
+ "angle": 180,
1011
+ "stops": [
1012
+ { "offset": 0, "color": "#EFF6FF" },
1013
+ { "offset": 1, "color": "#DBEAFE" }
1014
+ ]
1015
+ },
1016
+ "dark-poster": {
1017
+ "type": "image",
1018
+ "src": "backgrounds/dark-poster.png",
1019
+ "fit": "cover",
1020
+ "overlay": {
1021
+ "color": "#020617",
1022
+ "opacity": 0.32
1023
+ }
1024
+ }
1025
+ },
1026
+ "backgroundPreset": "blue-soft",
1027
+ "titleColor": "#0F172A",
1028
+ "subtitleColor": "#475569"
1029
+ },
1030
+ "targets": ["iphone-6.5"],
1031
+ "locales": ["zh-Hans"],
1032
+ "frames": [
1033
+ {
1034
+ "name": "01_home",
1035
+ "raw": "raw/{locale}/01_home.png",
1036
+ "title": "快速完成估价",
1037
+ "subtitle": "使用默认 blue-soft 背景"
1038
+ },
1039
+ {
1040
+ "name": "02_detail",
1041
+ "raw": "raw/{locale}/02_detail.png",
1042
+ "title": "详情信息完整展示",
1043
+ "subtitle": "单张图切换为 dark-poster 背景",
1044
+ "backgroundPreset": "dark-poster",
1045
+ "titleColor": "#FFFFFF",
1046
+ "subtitleColor": "#E2E8F0"
1047
+ }
1048
+ ]
1049
+ }
1050
+ ```
1051
+
1052
+ 也可以使用对象形式引用预设:
1053
+
1054
+ ```json
1055
+ {
1056
+ "frames": [
1057
+ {
1058
+ "name": "01_home",
1059
+ "raw": "raw/{locale}/01_home.png",
1060
+ "background": {
1061
+ "type": "preset",
1062
+ "name": "blue-soft"
1063
+ }
1064
+ }
1065
+ ]
1066
+ }
1067
+ ```
1068
+
1069
+ ##### 多语言和多设备配置
1070
+
1071
+ `targets` 和 `locales` 会做笛卡尔组合。下面配置会输出 `2 个设备 × 2 个语言 × 2 个页面 = 8 张图`。
1072
+
1073
+ ```json
1074
+ {
1075
+ "version": 1,
1076
+ "platform": "ios",
1077
+ "defaults": {
1078
+ "backgroundPreset": "blue-soft",
1079
+ "backgroundPresets": {
1080
+ "blue-soft": {
1081
+ "type": "linear-gradient",
1082
+ "angle": 180,
1083
+ "stops": [
1084
+ { "offset": 0, "color": "#F8FAFC" },
1085
+ { "offset": 1, "color": "#DBEAFE" }
1086
+ ]
1087
+ }
1088
+ }
1089
+ },
1090
+ "targets": ["iphone-6.9", "iphone-6.5"],
1091
+ "locales": ["zh-Hans", "en-US"],
1092
+ "frames": [
1093
+ {
1094
+ "name": "01_home",
1095
+ "raw": "raw/{locale}/01_home.png",
1096
+ "title": "快速完成估价",
1097
+ "subtitle": "关键流程清晰可见"
1098
+ },
1099
+ {
1100
+ "name": "02_detail",
1101
+ "raw": "raw/{locale}/{target}/02_detail.png",
1102
+ "title": "详情信息完整展示",
1103
+ "subtitle": "不同设备可使用不同原图"
1104
+ }
1105
+ ]
1106
+ }
1107
+ ```
1108
+
1109
+ 对应的原图目录可以这样组织:
1110
+
1111
+ ```text
1112
+ raw/
1113
+ zh-Hans/
1114
+ 01_home.png
1115
+ iphone-6.9/
1116
+ 02_detail.png
1117
+ iphone-6.5/
1118
+ 02_detail.png
1119
+ en-US/
1120
+ 01_home.png
1121
+ iphone-6.9/
1122
+ 02_detail.png
1123
+ iphone-6.5/
1124
+ 02_detail.png
1125
+ ```
1126
+
1127
+ ##### 单张图只对指定语言或设备生效
1128
+
1129
+ `frames[].target` 和 `frames[].locale` 可以限制某一张图只在指定组合下输出。下面示例中,`03_ipad_dashboard` 只会输出 iPad 图,`04_cn_campaign` 只会输出中文图。
1130
+
1131
+ ```json
1132
+ {
1133
+ "targets": ["iphone-6.5", "ipad-13"],
1134
+ "locales": ["zh-Hans", "en-US"],
1135
+ "frames": [
1136
+ {
1137
+ "name": "01_home",
1138
+ "raw": "raw/{locale}/01_home.png",
1139
+ "title": "快速完成估价"
1140
+ },
1141
+ {
1142
+ "name": "03_ipad_dashboard",
1143
+ "raw": "raw/{locale}/ipad-dashboard.png",
1144
+ "title": "大屏数据总览",
1145
+ "target": "ipad-13"
1146
+ },
1147
+ {
1148
+ "name": "04_cn_campaign",
1149
+ "raw": "raw/zh-Hans/campaign.png",
1150
+ "title": "中文专属活动页",
1151
+ "locale": "zh-Hans",
1152
+ "background": {
1153
+ "type": "linear-gradient",
1154
+ "angle": 135,
1155
+ "stops": [
1156
+ { "offset": 0, "color": "#FEF3C7" },
1157
+ { "offset": 1, "color": "#F97316" }
1158
+ ]
1159
+ }
1160
+ }
1161
+ ]
1162
+ }
1163
+ ```
1164
+
1165
+ ##### 完整示例
1166
+
1167
+ 下面是一份可以直接保存为 `screenshot.config.json` 的完整示例。它同时包含多设备、多语言、背景预设、图片背景、渐变背景和单张图覆盖配置。
1168
+
1169
+ ```json
1170
+ {
1171
+ "version": 1,
1172
+ "platform": "ios",
1173
+ "defaults": {
1174
+ "backgroundPresets": {
1175
+ "soft-brand": {
1176
+ "type": "stack",
1177
+ "layers": [
1178
+ {
1179
+ "type": "linear-gradient",
1180
+ "angle": 180,
1181
+ "stops": [
1182
+ { "offset": 0, "color": "#F8FAFC" },
1183
+ { "offset": 0.5, "color": "#E0F2FE" },
1184
+ { "offset": 1, "color": "#DBEAFE" }
1185
+ ]
1186
+ },
1187
+ {
1188
+ "type": "image",
1189
+ "src": "backgrounds/noise.png",
1190
+ "fit": "cover",
1191
+ "opacity": 0.1,
1192
+ "blend": "overlay"
1193
+ }
1194
+ ]
1195
+ },
1196
+ "poster": {
1197
+ "type": "image",
1198
+ "src": "backgrounds/{locale}/{frame}.png",
1199
+ "fit": "cover",
1200
+ "position": "center",
1201
+ "overlay": {
1202
+ "color": "#020617",
1203
+ "opacity": 0.24
1204
+ }
1205
+ }
1206
+ },
1207
+ "backgroundPreset": "soft-brand",
1208
+ "titleColor": "#0F172A",
1209
+ "subtitleColor": "#475569",
1210
+ "fontFamily": "Inter, Arial, sans-serif",
1211
+ "orientation": "portrait",
1212
+ "deviceWidthRatio": 0.76
1213
+ },
1214
+ "targets": ["iphone-6.9", "iphone-6.5"],
1215
+ "locales": ["zh-Hans", "en-US"],
1216
+ "frames": [
1217
+ {
1218
+ "name": "01_home",
1219
+ "raw": "raw/{locale}/01_home.png",
1220
+ "title": "快速完成估价",
1221
+ "subtitle": "关键流程清晰可见",
1222
+ "layout": "device-with-title-subtitle"
1223
+ },
1224
+ {
1225
+ "name": "02_detail",
1226
+ "raw": "raw/{locale}/{target}/02_detail.png",
1227
+ "title": "详情信息完整展示",
1228
+ "subtitle": "不同设备使用不同原图",
1229
+ "layout": "device-with-title-subtitle"
1230
+ },
1231
+ {
1232
+ "name": "03_story",
1233
+ "raw": "raw/{locale}/03_story.png",
1234
+ "title": "品牌故事完整呈现",
1235
+ "subtitle": "这一页使用图片背景预设",
1236
+ "backgroundPreset": "poster",
1237
+ "titleColor": "#FFFFFF",
1238
+ "subtitleColor": "#E2E8F0"
1239
+ },
1240
+ {
1241
+ "name": "04_fullscreen",
1242
+ "raw": "raw/{locale}/04_fullscreen.png",
1243
+ "layout": "raw-fullscreen",
1244
+ "rawFit": "cover"
1245
+ }
1246
+ ]
1247
+ }
1248
+ ```
588
1249
 
589
1250
  尺寸和输出规则:
590
1251
 
@@ -985,7 +1646,7 @@ ipa-cli assets manifest . --scope children --output ./assets-manifest.json
985
1646
  能力说明如下:
986
1647
 
987
1648
  - 跨平台稳定可用:IPA 解包、`Info.plist` 解析、资源扫描、指纹 JSON 分析、相似度对比、图片哈希检测
988
- - macOS 上信号更完整:如果系统提供 `codesign`、`otool`、`nm`、`xcrun assetutil`,会补充 entitlements、Mach-O 高级信号、`Assets.car` 高级摘要
1649
+ - macOS 上信号更完整:如果系统提供 `codesign`、`otool`、`nm`、`xcrun assetutil`,会补充 entitlements、Mach-O 高级信号、`Assets.car` 高级摘要;可用 `ipa-cli doctor` 自检并查看安装建议
989
1650
  - Linux / Windows 为受控降级:缺少 Apple 专有工具时,CLI 会标记相关能力为降级,但不会因为这些工具缺失而整体失败
990
1651
 
991
1652
  ## 当前边界