sentry-cli 2.46.0__tar.gz → 2.47.0__tar.gz

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 (168) hide show
  1. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/Cargo.lock +10 -2
  2. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/Cargo.toml +18 -2
  3. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/MANIFEST.in +1 -0
  4. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/PKG-INFO +2 -2
  5. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/README.md +1 -1
  6. sentry_cli-2.47.0/apple-catalog-parsing/Cargo.toml +10 -0
  7. sentry_cli-2.47.0/apple-catalog-parsing/build.rs +81 -0
  8. sentry_cli-2.47.0/apple-catalog-parsing/native/swift/AssetCatalogParser/Package.swift +33 -0
  9. sentry_cli-2.47.0/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift +301 -0
  10. sentry_cli-2.47.0/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/ObjcSupport/include/safeValueForKey.h +10 -0
  11. sentry_cli-2.47.0/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/ObjcSupport/safeValueForKey.m +11 -0
  12. sentry_cli-2.47.0/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/AssetCatalogParserTests.swift +12 -0
  13. sentry_cli-2.47.0/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/Resources/test.xcarchive/Info.plist +33 -0
  14. sentry_cli-2.47.0/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/Resources/test.xcarchive/Products/Applications/DemoApp.app/Assets.car +0 -0
  15. sentry_cli-2.47.0/apple-catalog-parsing/src/asset_catalog.rs +36 -0
  16. sentry_cli-2.47.0/apple-catalog-parsing/src/lib.rs +5 -0
  17. sentry_cli-2.47.0/build.rs +33 -0
  18. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/sentry_cli.egg-info/PKG-INFO +2 -2
  19. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/sentry_cli.egg-info/SOURCES.txt +17 -0
  20. sentry_cli-2.47.0/src/api/data_types/chunking/mobile_app.rs +23 -0
  21. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/mod.rs +3 -0
  22. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/upload/capability.rs +4 -0
  23. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/mod.rs +29 -3
  24. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/bash_hook.rs +4 -2
  25. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/debug_files/bundle_jvm.rs +2 -0
  26. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/debug_files/bundle_sources.rs +2 -0
  27. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/debug_files/check.rs +2 -0
  28. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/debug_files/find.rs +1 -0
  29. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/debug_files/print_sources.rs +1 -0
  30. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/deploys/list.rs +1 -0
  31. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/deploys/new.rs +5 -3
  32. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/events/list.rs +1 -0
  33. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/files/upload.rs +2 -0
  34. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/issues/list.rs +1 -0
  35. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/login.rs +60 -4
  36. sentry_cli-2.47.0/src/commands/mobile_app/mod.rs +49 -0
  37. sentry_cli-2.47.0/src/commands/mobile_app/upload.rs +404 -0
  38. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/mod.rs +3 -0
  39. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/monitors/run.rs +2 -0
  40. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/react_native/appcenter.rs +2 -0
  41. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/react_native/gradle.rs +3 -2
  42. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/react_native/xcode.rs +3 -1
  43. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/archive.rs +1 -0
  44. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/delete.rs +1 -0
  45. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/finalize.rs +1 -0
  46. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/info.rs +1 -0
  47. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/new.rs +1 -0
  48. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/restore.rs +1 -0
  49. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/set_commits.rs +2 -0
  50. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/send_envelope.rs +2 -0
  51. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/send_event.rs +1 -0
  52. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/sourcemaps/explain.rs +2 -0
  53. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/sourcemaps/inject.rs +1 -0
  54. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/sourcemaps/resolve.rs +2 -7
  55. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/sourcemaps/upload.rs +2 -0
  56. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/upload_proguard.rs +1 -0
  57. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/config.rs +2 -2
  58. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/main.rs +0 -2
  59. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/args.rs +1 -0
  60. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/auth_token/org_auth_token.rs +0 -2
  61. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/auth_token/redacting.rs +1 -1
  62. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/auth_token/user_auth_token.rs +1 -0
  63. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/dif_upload/error.rs +1 -1
  64. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/dif_upload/mod.rs +3 -1
  65. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/event.rs +1 -1
  66. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/file_search.rs +3 -4
  67. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/file_upload.rs +2 -2
  68. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/formatting.rs +1 -0
  69. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/http.rs +2 -0
  70. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/logging.rs +0 -24
  71. sentry_cli-2.47.0/src/utils/mobile_app/apple.rs +29 -0
  72. sentry_cli-2.47.0/src/utils/mobile_app/mod.rs +9 -0
  73. sentry_cli-2.47.0/src/utils/mobile_app/validation.rs +56 -0
  74. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/mod.rs +1 -0
  75. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/releases.rs +2 -0
  76. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/sourcemaps/inject.rs +1 -1
  77. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/sourcemaps.rs +5 -3
  78. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/system.rs +5 -6
  79. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/ui.rs +1 -0
  80. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/update.rs +2 -0
  81. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/vcs.rs +2 -0
  82. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/xcode.rs +2 -0
  83. sentry_cli-2.46.0/build.rs +0 -30
  84. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/LICENSE +0 -0
  85. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/pyproject.toml +0 -0
  86. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/sentry_cli.egg-info/dependency_links.txt +0 -0
  87. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/sentry_cli.egg-info/top_level.txt +0 -0
  88. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/setup.cfg +0 -0
  89. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/setup.py +0 -0
  90. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/connection_manager.rs +0 -0
  91. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/artifact.rs +0 -0
  92. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/compression.rs +0 -0
  93. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/dif.rs +0 -0
  94. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/file_state.rs +0 -0
  95. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/hash_algorithm.rs +0 -0
  96. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/upload/mod.rs +0 -0
  97. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/chunking/upload/options.rs +0 -0
  98. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/deploy.rs +0 -0
  99. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/data_types/mod.rs +0 -0
  100. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/encoding.rs +0 -0
  101. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/envelopes_api.rs +0 -0
  102. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/errors/api_error.rs +0 -0
  103. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/errors/mod.rs +0 -0
  104. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/errors/sentry_error.rs +0 -0
  105. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/api/pagination.rs +0 -0
  106. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/bashsupport.sh +0 -0
  107. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/debug_files/mod.rs +0 -0
  108. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/debug_files/upload.rs +0 -0
  109. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/deploys/mod.rs +0 -0
  110. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/derive_parser.rs +0 -0
  111. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/events/mod.rs +0 -0
  112. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/files/delete.rs +0 -0
  113. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/files/list.rs +0 -0
  114. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/files/mod.rs +0 -0
  115. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/info.rs +0 -0
  116. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/issues/mod.rs +0 -0
  117. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/issues/mute.rs +0 -0
  118. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/issues/resolve.rs +0 -0
  119. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/issues/unresolve.rs +0 -0
  120. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/monitors/list.rs +0 -0
  121. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/monitors/mod.rs +0 -0
  122. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/organizations/list.rs +0 -0
  123. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/organizations/mod.rs +0 -0
  124. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/projects/list.rs +0 -0
  125. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/projects/mod.rs +0 -0
  126. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/react_native/mod.rs +0 -0
  127. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/list.rs +0 -0
  128. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/mod.rs +0 -0
  129. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/releases/propose_version.rs +0 -0
  130. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/repos/list.rs +0 -0
  131. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/repos/mod.rs +0 -0
  132. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/send_metric/common_args.rs +0 -0
  133. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/send_metric/distribution.rs +0 -0
  134. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/send_metric/gauge.rs +0 -0
  135. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/send_metric/increment.rs +0 -0
  136. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/send_metric/mod.rs +0 -0
  137. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/send_metric/set.rs +0 -0
  138. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/sourcemaps/mod.rs +0 -0
  139. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/uninstall.rs +0 -0
  140. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/update.rs +0 -0
  141. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/upload_dif.rs +0 -0
  142. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/commands/upload_dsym.rs +0 -0
  143. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/constants.rs +0 -0
  144. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/android.rs +0 -0
  145. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/appcenter.rs +0 -0
  146. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/auth_token/auth_token_impl.rs +0 -0
  147. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/auth_token/error.rs +0 -0
  148. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/auth_token/mod.rs +0 -0
  149. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/auth_token/test.rs +0 -0
  150. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/chunks/mod.rs +0 -0
  151. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/chunks/options.rs +0 -0
  152. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/chunks/types.rs +0 -0
  153. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/chunks/upload.rs +0 -0
  154. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/cordova.rs +0 -0
  155. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/dif.rs +0 -0
  156. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/fs.rs +0 -0
  157. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/metrics.rs +0 -0
  158. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/progress.rs +0 -0
  159. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/proguard/mapping.rs +0 -0
  160. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/proguard/mod.rs +0 -0
  161. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/proguard/upload.rs +0 -0
  162. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/retry.rs +0 -0
  163. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/snapshots/sentry_cli__utils__vcs__generate_patch_default_twenty.snap +0 -0
  164. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/snapshots/sentry_cli__utils__vcs__generate_patch_ignore_missing.snap +0 -0
  165. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/snapshots/sentry_cli__utils__vcs__generate_patch_set_base.snap +0 -0
  166. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/snapshots/sentry_cli__utils__vcs__generate_patch_set_previous_commit.snap +0 -0
  167. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/snapshots/sentry_cli__utils__vcs__get_commits_from_git.snap +0 -0
  168. {sentry_cli-2.46.0 → sentry_cli-2.47.0}/src/utils/value_parsers.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  # This file is automatically @generated by Cargo.
2
2
  # It is not intended for manual editing.
3
- version = 3
3
+ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "addr2line"
@@ -144,6 +144,13 @@ dependencies = [
144
144
  "regex",
145
145
  ]
146
146
 
147
+ [[package]]
148
+ name = "apple-catalog-parsing"
149
+ version = "0.0.0"
150
+ dependencies = [
151
+ "thiserror 2.0.12",
152
+ ]
153
+
147
154
  [[package]]
148
155
  name = "arbitrary"
149
156
  version = "1.4.1"
@@ -2685,10 +2692,11 @@ dependencies = [
2685
2692
 
2686
2693
  [[package]]
2687
2694
  name = "sentry-cli"
2688
- version = "2.46.0"
2695
+ version = "2.47.0"
2689
2696
  dependencies = [
2690
2697
  "anyhow",
2691
2698
  "anylog",
2699
+ "apple-catalog-parsing",
2692
2700
  "assert_cmd",
2693
2701
  "backoff",
2694
2702
  "brotli2",
@@ -2,13 +2,16 @@
2
2
  authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
3
3
  build = "build.rs"
4
4
  name = "sentry-cli"
5
- version = "2.46.0"
5
+ version = "2.47.0"
6
6
  edition = "2021"
7
- rust-version = "1.80"
7
+ rust-version = "1.86"
8
+
9
+ [workspace]
8
10
 
9
11
  [dependencies]
10
12
  anylog = "0.6.3"
11
13
  anyhow = { version = "1.0.69", features = ["backtrace"] }
14
+ apple-catalog-parsing = { path = "apple-catalog-parsing", optional = true }
12
15
  backoff = "0.4.0"
13
16
  brotli2 = "0.3.2"
14
17
  bytecount = "0.6.3"
@@ -91,12 +94,25 @@ default = []
91
94
  managed = []
92
95
  with_crash_reporting = []
93
96
 
97
+ # Feature flag for the mobile-app command, as it is still under development.
98
+ # CI tests run against this flag, but we don't include it in release builds.
99
+ unstable-mobile-app = ["apple-catalog-parsing"]
100
+
101
+ [workspace.lints.clippy]
102
+ allow-attributes = "warn"
103
+ unnecessary-wraps = "warn"
104
+ unwrap-used = "warn"
105
+
106
+ [lints]
107
+ workspace = true
108
+
94
109
  [target]
95
110
 
96
111
  [target."cfg(target_os = \"macos\")"]
97
112
 
98
113
  [target."cfg(target_os = \"macos\")".dependencies]
99
114
  mac-process-info = "0.2.0"
115
+ apple-catalog-parsing = { path = "apple-catalog-parsing" }
100
116
 
101
117
  [target."cfg(unix)"]
102
118
 
@@ -1,2 +1,3 @@
1
1
  include build.rs Cargo.toml Cargo.lock
2
2
  recursive-include src *
3
+ recursive-include apple-catalog-parsing *
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentry_cli
3
- Version: 2.46.0
3
+ Version: 2.47.0
4
4
  Summary: A command line utility to work with Sentry.
5
5
  Home-page: https://github.com/getsentry/sentry-cli
6
6
  Author: Sentry
@@ -43,7 +43,7 @@ Fastlane tools.
43
43
 
44
44
  ## Installation
45
45
 
46
- If you are on OS X or Linux, you can use the automated downloader which will fetch the latest release version for you and install it:
46
+ If you are on macOS or Linux, you can use the automated downloader which will fetch the latest release version for you and install it:
47
47
 
48
48
  curl -sL https://sentry.io/get-cli/ | bash
49
49
 
@@ -25,7 +25,7 @@ Fastlane tools.
25
25
 
26
26
  ## Installation
27
27
 
28
- If you are on OS X or Linux, you can use the automated downloader which will fetch the latest release version for you and install it:
28
+ If you are on macOS or Linux, you can use the automated downloader which will fetch the latest release version for you and install it:
29
29
 
30
30
  curl -sL https://sentry.io/get-cli/ | bash
31
31
 
@@ -0,0 +1,10 @@
1
+ [package]
2
+ name = "apple-catalog-parsing"
3
+ edition = "2021"
4
+ build = "build.rs"
5
+
6
+ [dependencies]
7
+ thiserror = "2.0.12"
8
+
9
+ [lints]
10
+ workspace = true
@@ -0,0 +1,81 @@
1
+ use std::env;
2
+ use std::process::Command;
3
+
4
+ fn main() {
5
+ let target = env::var("TARGET").expect("TARGET is set for build scripts");
6
+ let mut target_bits = target.split('-');
7
+
8
+ // https://rust-lang.github.io/rfcs/0131-target-specification.html#detailed-design
9
+ let mut arch = target_bits.next().expect("TARGET triple has an arch");
10
+ let _vendor = target_bits.next();
11
+ let platform = target_bits.next().expect("TARGET triple has a platform");
12
+
13
+ if platform != "darwin" {
14
+ return;
15
+ }
16
+
17
+ if arch == "aarch64" {
18
+ arch = "arm64"; // enforce Darwin naming conventions
19
+ }
20
+
21
+ println!("cargo:rerun-if-changed=native/swift/AssetCatalogParser");
22
+
23
+ let out_dir = env::var("OUT_DIR").expect("OUT_DIR is set for build scripts");
24
+
25
+ // Compile Swift code
26
+ let status = Command::new("swift")
27
+ .args([
28
+ "build",
29
+ "-c",
30
+ "release",
31
+ "--package-path",
32
+ "native/swift/AssetCatalogParser",
33
+ "--scratch-path",
34
+ &format!("{out_dir}/swift-scratch"),
35
+ "--triple",
36
+ &format!("{arch}-apple-macosx10.12"),
37
+ ])
38
+ .status()
39
+ .expect("Failed to compile SPM");
40
+
41
+ assert!(status.success(), "swift build failed");
42
+
43
+ // Create a static library of the Swift and Objective-C code
44
+ let status = Command::new("ar")
45
+ .args([
46
+ "crus",
47
+ &format!("{out_dir}/libswiftbridge.a"),
48
+ &format!(
49
+ "{out_dir}/swift-scratch/release/AssetCatalogParser.build/AssetCatalogReader.swift.o"
50
+ ),
51
+ &format!(
52
+ "{out_dir}/swift-scratch/release/ObjcSupport.build/safeValueForKey.m.o",
53
+ ),
54
+ ])
55
+ .status()
56
+ .expect("Failed to create static library");
57
+
58
+ assert!(status.success(), "ar failed");
59
+
60
+ // Add the new static library to search paths and link to it
61
+ println!("cargo:rustc-link-search=native={out_dir}");
62
+ println!("cargo:rustc-link-lib=static=swiftbridge");
63
+
64
+ // Link to CoreUI framework
65
+ println!("cargo:rustc-link-search=framework=/System/Library/PrivateFrameworks");
66
+ println!("cargo:rustc-link-lib=framework=CoreUI");
67
+
68
+ // Link to swift macOS support libraries for Swift runtime support on older macOS versions
69
+ let developer_dir = Command::new("xcode-select")
70
+ .args(["-p"])
71
+ .output()
72
+ .expect("Failed to get developer directory, please ensure Xcode is installed.");
73
+ let developer_dir_path = String::from_utf8(developer_dir.stdout)
74
+ .expect("Failed to convert developer directory to UTF-8")
75
+ .trim()
76
+ .to_string();
77
+
78
+ println!(
79
+ "cargo:rustc-link-search={developer_dir_path}/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx"
80
+ );
81
+ }
@@ -0,0 +1,33 @@
1
+ // swift-tools-version: 5.10
2
+
3
+ import PackageDescription
4
+
5
+ let package = Package(
6
+ name: "AssetCatalogParser",
7
+ platforms: [
8
+ .macOS(.v11),
9
+ ],
10
+ products: [
11
+ .library(
12
+ name: "AssetCatalogParser",
13
+ targets: ["AssetCatalogParser"]),
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "AssetCatalogParser",
18
+ dependencies: ["ObjcSupport"],
19
+ linkerSettings: [
20
+ .unsafeFlags([
21
+ "-F", "/System/Library/PrivateFrameworks",
22
+ "-framework", "CoreUI",
23
+ ])
24
+ ]),
25
+ .target(name: "ObjcSupport"),
26
+ .testTarget(
27
+ name: "AssetCatalogParserTests",
28
+ dependencies: ["AssetCatalogParser"],
29
+ resources: [
30
+ .copy("Resources")
31
+ ])
32
+ ]
33
+ )
@@ -0,0 +1,301 @@
1
+ import CoreGraphics
2
+ import Foundation
3
+ import ImageIO
4
+ import UniformTypeIdentifiers
5
+ import ObjcSupport
6
+
7
+ @_cdecl("swift_inspect_asset_catalog")
8
+ // Insepects the asset catalog and writes the results to a JSON file
9
+ // in the xcarchive containing the asset catalog.
10
+ public func swift_inspect_asset_catalog(_ path: UnsafePointer<CChar>) {
11
+ let pathString = String(cString: path)
12
+ if #available(macOS 13.0, *) {
13
+ let supportedVersions = [13, 14, 15]
14
+ let version = ProcessInfo.processInfo.operatingSystemVersion
15
+ if supportedVersions.contains(version.majorVersion) {
16
+ AssetUtil.disect(file: URL(filePath: pathString))
17
+ } else {
18
+ print("Skipping asset catalog inspection on unsupported macOS version \(version)")
19
+ }
20
+ } else {
21
+ print("Skipping asset catalog inspection on macOS earlier than 13.0")
22
+ }
23
+ }
24
+
25
+ enum AssetType: Int, Encodable {
26
+ case image
27
+ case icon
28
+ case imageSet
29
+ }
30
+
31
+ struct AssetCatalogEntry: Encodable {
32
+ let imageId: String
33
+ let size: UInt
34
+ let name: String
35
+ let vector: Bool
36
+ let width: Int?
37
+ let height: Int?
38
+ let filename: String?
39
+ let type: AssetType?
40
+ }
41
+
42
+ enum Error: Swift.Error {
43
+ case pathError
44
+ }
45
+
46
+ typealias objectiveCMethodImp = @convention(c) (AnyObject, Selector, UnsafeRawPointer) -> Unmanaged<
47
+ AnyObject
48
+ >?
49
+
50
+ enum AssetUtil {
51
+ private static func createResultsPath(assetPath: URL) throws -> URL {
52
+ var archiveURL = assetPath
53
+ var tailComponents: [String] = []
54
+ while archiveURL.pathExtension != "xcarchive" && archiveURL.pathComponents.count > 1 {
55
+ tailComponents.insert(archiveURL.lastPathComponent, at: 0)
56
+ archiveURL.deleteLastPathComponent()
57
+ }
58
+ if archiveURL.pathExtension != "xcarchive" {
59
+ throw Error.pathError
60
+ }
61
+ let parsedRoot = archiveURL.appendingPathComponent("ParsedAssets",
62
+ isDirectory: true)
63
+ let destDir = tailComponents
64
+ .dropLast()
65
+ .reduce(parsedRoot) { partial, next in
66
+ partial.appendingPathComponent(next, isDirectory: true)
67
+ }
68
+ try! FileManager.default.createDirectory(at: destDir,
69
+ withIntermediateDirectories: true)
70
+ return destDir
71
+ }
72
+
73
+ @discardableResult static func disect(file: URL) -> [AssetCatalogEntry] {
74
+ var assets: [AssetCatalogEntry] = []
75
+ var colorLength: UInt = 0
76
+ var colorCount = 0
77
+
78
+ let (structuredThemeStore, assetKeys) = initializeCatalog(from: file)
79
+
80
+ var images: [String: CGImage] = [:]
81
+
82
+ for key in assetKeys {
83
+ let keyList = unsafeBitCast(
84
+ key.perform(Selector(("keyList"))),
85
+ to: UnsafeMutableRawPointer.self
86
+ )
87
+ let rendition = createRendition(from: structuredThemeStore, keyList)
88
+
89
+ let data = rendition.value(forKey: "_srcData") as! Data
90
+ let length = UInt(data.count)
91
+ let className = rendition.perform(Selector(("className"))).takeUnretainedValue() as! String
92
+ let renditionTypeName =
93
+ rendition.perform(Selector(("name"))).takeUnretainedValue() as! String
94
+
95
+ var packedAssetSize: UInt = 0
96
+ if renditionTypeName.hasPrefix("ZZZZPacked") {
97
+ packedAssetSize += length
98
+ continue
99
+ }
100
+
101
+ if handleReferenceKey(
102
+ rendition,
103
+ structuredThemeStore,
104
+ Selector(("renditionWithKey:")),
105
+ &packedAssetSize,
106
+ renditionTypeName,
107
+ length
108
+ ) {
109
+ continue
110
+ }
111
+
112
+ if className == "_CUIThemeColorRendition" {
113
+ colorCount += 1
114
+ colorLength += length
115
+ continue
116
+ }
117
+
118
+ let name = resolveRenditionName(
119
+ structuredThemeStore,
120
+ keyList,
121
+ renditionTypeName
122
+ )
123
+
124
+ let type = rendition.getUInt(forKey: "type") ?? 0
125
+
126
+ let isVector = type == 9
127
+ let (width, height, unslicedImage) = resolveImageDimensions(rendition, isVector)
128
+ let assetType = determineAssetType(key)
129
+ let imageId = UUID().uuidString
130
+ images[imageId] = unslicedImage
131
+
132
+ let asset = AssetCatalogEntry(
133
+ imageId: imageId,
134
+ size: length,
135
+ name: name,
136
+ vector: isVector,
137
+ width: width,
138
+ height: height,
139
+ filename: renditionTypeName,
140
+ type: assetType
141
+ )
142
+ assets.append(asset)
143
+ }
144
+
145
+ assets.append(AssetCatalogEntry(
146
+ imageId: "",
147
+ size: colorLength,
148
+ name: "\(colorCount) Color\(colorCount > 1 ? "s" : "")",
149
+ vector: false,
150
+ width: nil,
151
+ height: nil,
152
+ filename: nil,
153
+ type: nil
154
+ ))
155
+
156
+ let data = try! JSONEncoder().encode(assets)
157
+ let folder = try! createResultsPath(assetPath: file)
158
+ let url = folder
159
+ .appendingPathComponent("Assets")
160
+ .appendingPathExtension("json")
161
+ try! data.write(to: url, options: [])
162
+ for (id, cgImage) in images {
163
+ let fileURL = folder.appendingPathComponent(id)
164
+ .appendingPathExtension("png")
165
+
166
+ guard let dest = CGImageDestinationCreateWithURL(
167
+ fileURL as CFURL,
168
+ UTType.png.identifier as CFString,
169
+ 1,
170
+ nil
171
+ )
172
+ else {
173
+ print("⚠️ Could not create destination for \(fileURL.path)")
174
+ continue
175
+ }
176
+
177
+ CGImageDestinationAddImage(dest, cgImage, nil)
178
+ CGImageDestinationFinalize(dest)
179
+ }
180
+ return assets
181
+ }
182
+
183
+ private static func initializeCatalog(from file: URL) -> (
184
+ themeStore: NSObject, assetKeys: [NSObject]
185
+ ) {
186
+ let catalogClass: NSObject.Type = NSClassFromString("CUICatalog")! as! NSObject.Type
187
+ var catalog: NSObject =
188
+ catalogClass.perform(Selector(("alloc"))).takeRetainedValue() as! NSObject
189
+ catalog =
190
+ catalog.perform(Selector(("initWithURL:error:")), with: file as NSURL, with: nil)
191
+ .takeUnretainedValue() as! NSObject
192
+ let structuredThemeStore =
193
+ catalog.perform(Selector(("_themeStore"))).takeUnretainedValue() as! NSObject
194
+ let assetStorage = structuredThemeStore.perform(Selector(("themeStore"))).takeUnretainedValue()
195
+ let assetKeys =
196
+ assetStorage.perform(Selector(("allAssetKeys"))).takeUnretainedValue() as! [NSObject]
197
+ return (structuredThemeStore, assetKeys)
198
+ }
199
+
200
+ private static func createRendition(from themeStore: NSObject, _ keyList: UnsafeMutableRawPointer)
201
+ -> NSObject
202
+ {
203
+ let renditionWithKeySelector = Selector(("renditionWithKey:"))
204
+ let renditionWithKeyMethod = themeStore.method(for: renditionWithKeySelector)!
205
+ let renditionWithKeyImp = unsafeBitCast(renditionWithKeyMethod, to: objectiveCMethodImp.self)
206
+ return renditionWithKeyImp(themeStore, renditionWithKeySelector, keyList)!.takeUnretainedValue()
207
+ as! NSObject
208
+ }
209
+
210
+ private static func handleReferenceKey(
211
+ _ rendition: NSObject,
212
+ _ themeStore: NSObject,
213
+ _: Selector,
214
+ _ packedAssetSize: inout UInt,
215
+ _: String,
216
+ _ length: UInt
217
+ ) -> Bool {
218
+ let referenceKey = safeValueForKey(rendition, "_referenceKey")
219
+ guard let referenceKey = referenceKey as? NSObject else { return false }
220
+
221
+ let referenceKeyList = unsafeBitCast(
222
+ referenceKey.perform(Selector(("keyList"))),
223
+ to: UnsafeMutableRawPointer.self
224
+ )
225
+ let referenceRendition = createRendition(from: themeStore, referenceKeyList)
226
+
227
+ if let result = referenceRendition.perform(Selector(("unslicedImage"))) {
228
+ let image = result.takeUnretainedValue() as! CGImage
229
+ if image.dataProvider?.data as? Data != nil {
230
+ packedAssetSize += length
231
+ }
232
+ }
233
+ return true
234
+ }
235
+
236
+ private static func determineAssetType(_ key: NSObject) -> AssetType {
237
+ let themeElement = key.getUInt(forKey: "themeElement") ?? 0
238
+ let themePart = key.getUInt(forKey: "themePart") ?? 0
239
+
240
+ if themeElement == 85, themePart == 220 {
241
+ return .icon
242
+ } else if themeElement == 9 {
243
+ return .imageSet
244
+ }
245
+ return .image
246
+ }
247
+
248
+ private static func resolveRenditionName(
249
+ _ structuredThemeStore: NSObject,
250
+ _ keyList: UnsafeMutableRawPointer,
251
+ _ renditionTypeName: String
252
+ ) -> String {
253
+ let renditionNameForKeyListSelector = Selector(("renditionNameForKeyList:"))
254
+ let renditionNameForKeyListMethod = structuredThemeStore.method(
255
+ for: renditionNameForKeyListSelector
256
+ )!
257
+ let renditionNameForKeyList = unsafeBitCast(
258
+ renditionNameForKeyListMethod,
259
+ to: objectiveCMethodImp.self
260
+ )
261
+
262
+ var renditionName: String?
263
+ if let result = renditionNameForKeyList(
264
+ structuredThemeStore,
265
+ renditionNameForKeyListSelector,
266
+ keyList
267
+ ) {
268
+ renditionName = result.takeUnretainedValue() as? String
269
+ }
270
+
271
+ let name = renditionTypeName == "CoreStructuredImage" ? renditionName : renditionTypeName
272
+ return name!
273
+ }
274
+
275
+ private static func resolveImageDimensions(_ rendition: NSObject, _ isVector: Bool) -> (
276
+ width: Int?, height: Int?, image: CGImage?
277
+ ) {
278
+ var unslicedImage: CGImage?
279
+ if let result = rendition.perform(Selector(("unslicedImage"))) {
280
+ unslicedImage = (result.takeUnretainedValue() as! CGImage)
281
+ }
282
+
283
+ var width: Int?
284
+ var height: Int?
285
+ if !isVector {
286
+ width = unslicedImage?.width
287
+ height = unslicedImage?.height
288
+ }
289
+
290
+ return (width, height, unslicedImage)
291
+ }
292
+ }
293
+
294
+ private extension NSObject {
295
+ func getUInt(forKey key: String) -> UInt? {
296
+ if let result = perform(Selector(key)) {
297
+ return UInt(bitPattern: result.toOpaque())
298
+ }
299
+ return nil
300
+ }
301
+ }
@@ -0,0 +1,10 @@
1
+ //
2
+ // safeValueForKey.h
3
+ // AppSizeAnalyzer
4
+ //
5
+ // Created by Itay Brenner on 29/4/25.
6
+ //
7
+
8
+ #import <Foundation/Foundation.h>
9
+
10
+ id safeValueForKey(id object, NSString *key);
@@ -0,0 +1,11 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import "safeValueForKey.h"
3
+
4
+ // Helper to call `valueForKey` without swift crashing
5
+ id safeValueForKey(id object, NSString *key) {
6
+ @try {
7
+ return [object valueForKey:key];
8
+ } @catch (NSException *exception) {
9
+ return nil;
10
+ }
11
+ }
@@ -0,0 +1,12 @@
1
+ @testable import AssetCatalogParser
2
+ import Testing
3
+ import Foundation
4
+
5
+ struct AssetCatalogParserTests {
6
+ @Test func testParseAssets() throws {
7
+ let archivePath = try #require(Bundle.module.path(forResource: "test", ofType: "xcarchive"))
8
+ let url = URL(filePath: "\(archivePath)/Products/Applications/DemoApp.app/Assets.car")
9
+ let results = AssetUtil.disect(file: url)
10
+ #expect(results.count == 2)
11
+ }
12
+ }
@@ -0,0 +1,33 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>ApplicationProperties</key>
6
+ <dict>
7
+ <key>ApplicationPath</key>
8
+ <string>Applications/DemoApp.app</string>
9
+ <key>Architectures</key>
10
+ <array>
11
+ <string>arm64</string>
12
+ </array>
13
+ <key>CFBundleIdentifier</key>
14
+ <string>com.emerge.PreviewsDemoApp</string>
15
+ <key>CFBundleShortVersionString</key>
16
+ <string>1.0</string>
17
+ <key>CFBundleVersion</key>
18
+ <string>1</string>
19
+ <key>SigningIdentity</key>
20
+ <string>Apple Development: Noah Martin (5TV9RX846L)</string>
21
+ <key>Team</key>
22
+ <string>62J2XHNK9T</string>
23
+ </dict>
24
+ <key>ArchiveVersion</key>
25
+ <integer>2</integer>
26
+ <key>CreationDate</key>
27
+ <date>2024-03-02T16:16:02Z</date>
28
+ <key>Name</key>
29
+ <string>DemoApp</string>
30
+ <key>SchemeName</key>
31
+ <string>DemoApp</string>
32
+ </dict>
33
+ </plist>
@@ -0,0 +1,36 @@
1
+ use std::ffi::CString;
2
+ use std::os::unix::ffi::OsStrExt;
3
+ use std::path::Path;
4
+ use thiserror::Error;
5
+
6
+ #[derive(Error, Debug)]
7
+ #[non_exhaustive]
8
+ pub enum Error {
9
+ #[error("Failed to convert path to C string: {0}")]
10
+ PathConversion(#[from] std::ffi::NulError),
11
+ }
12
+
13
+ extern "C" {
14
+ fn swift_inspect_asset_catalog(msg: *const std::os::raw::c_char);
15
+ }
16
+
17
+ /// This calls out to Swift code that uses Apple APIs to convert the contents
18
+ /// of an asset catalog into a format that can be parsed by the
19
+ /// size analysis backend. It enables main size analysis features such
20
+ /// as duplicate image detection, xray, and image optimization insights.
21
+ /// The path should be in an xcarchive file, results are written
22
+ /// to a JSON file in the xcarchive’s ParsedAssets directory.
23
+ pub fn inspect_asset_catalog<P>(path: P) -> Result<(), Error>
24
+ where
25
+ P: AsRef<Path>,
26
+ {
27
+ let c_string = CString::new(path.as_ref().as_os_str().as_bytes())?;
28
+ let string_ptr = c_string.as_ptr();
29
+ unsafe {
30
+ // The string pointed to is immutable, in Swift we cannot change it.
31
+ // We ensure this by using "UnsafePointer<CChar>" in Swift which is
32
+ // immutable (as opposed to "UnsafeMutablePointer<CChar>").
33
+ swift_inspect_asset_catalog(string_ptr);
34
+ }
35
+ Ok(())
36
+ }
@@ -0,0 +1,5 @@
1
+ #![cfg(target_os = "macos")]
2
+
3
+ mod asset_catalog;
4
+
5
+ pub use asset_catalog::inspect_asset_catalog;