@sentry/wizard 6.11.0 → 6.13.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 (245) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/bin.js +16 -1
  3. package/dist/bin.js.map +1 -1
  4. package/dist/e2e-tests/tests/angular-17.test.js +3 -4
  5. package/dist/e2e-tests/tests/angular-17.test.js.map +1 -1
  6. package/dist/e2e-tests/tests/angular-19.test.js +3 -4
  7. package/dist/e2e-tests/tests/angular-19.test.js.map +1 -1
  8. package/dist/e2e-tests/tests/cloudflare-worker.test.js +5 -0
  9. package/dist/e2e-tests/tests/cloudflare-worker.test.js.map +1 -1
  10. package/dist/e2e-tests/tests/flutter.test.js +60 -0
  11. package/dist/e2e-tests/tests/flutter.test.js.map +1 -1
  12. package/dist/e2e-tests/tests/help-message.test.js +8 -3
  13. package/dist/e2e-tests/tests/help-message.test.js.map +1 -1
  14. package/dist/e2e-tests/tests/nuxt-3.test.js +12 -6
  15. package/dist/e2e-tests/tests/nuxt-3.test.js.map +1 -1
  16. package/dist/e2e-tests/tests/nuxt-4.test.js +12 -6
  17. package/dist/e2e-tests/tests/nuxt-4.test.js.map +1 -1
  18. package/dist/e2e-tests/tests/pnpm-workspace.test.js +8 -4
  19. package/dist/e2e-tests/tests/pnpm-workspace.test.js.map +1 -1
  20. package/dist/e2e-tests/tests/react-router-instrumentation-api.test.js +96 -0
  21. package/dist/e2e-tests/tests/react-router-instrumentation-api.test.js.map +1 -0
  22. package/dist/e2e-tests/tests/react-router.test.js +6 -7
  23. package/dist/e2e-tests/tests/react-router.test.js.map +1 -1
  24. package/dist/e2e-tests/tests/remix.test.js +2 -4
  25. package/dist/e2e-tests/tests/remix.test.js.map +1 -1
  26. package/dist/e2e-tests/tests/sveltekit-hooks.test.js +24 -8
  27. package/dist/e2e-tests/tests/sveltekit-hooks.test.js.map +1 -1
  28. package/dist/e2e-tests/tests/sveltekit-tracing.test.js +8 -4
  29. package/dist/e2e-tests/tests/sveltekit-tracing.test.js.map +1 -1
  30. package/dist/lib/Constants.d.ts +1 -0
  31. package/dist/lib/Constants.js +5 -0
  32. package/dist/lib/Constants.js.map +1 -1
  33. package/dist/lib/Steps/Integrations/Electron.js +2 -2
  34. package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
  35. package/dist/src/android/android-wizard.js +3 -0
  36. package/dist/src/android/android-wizard.js.map +1 -1
  37. package/dist/src/angular/codemods/main.d.ts +1 -1
  38. package/dist/src/angular/codemods/main.js +0 -1
  39. package/dist/src/angular/codemods/main.js.map +1 -1
  40. package/dist/src/apple/apple-wizard.js +2 -3
  41. package/dist/src/apple/apple-wizard.js.map +1 -1
  42. package/dist/src/apple/check-installed-cli.d.ts +1 -1
  43. package/dist/src/apple/check-installed-cli.js +13 -7
  44. package/dist/src/apple/check-installed-cli.js.map +1 -1
  45. package/dist/src/apple/code-tools.js +17 -3
  46. package/dist/src/apple/code-tools.js.map +1 -1
  47. package/dist/src/apple/configure-package-manager.js +18 -5
  48. package/dist/src/apple/configure-package-manager.js.map +1 -1
  49. package/dist/src/apple/configure-xcode-project.js +8 -1
  50. package/dist/src/apple/configure-xcode-project.js.map +1 -1
  51. package/dist/src/apple/lookup-xcode-project.d.ts +8 -5
  52. package/dist/src/apple/lookup-xcode-project.js +22 -17
  53. package/dist/src/apple/lookup-xcode-project.js.map +1 -1
  54. package/dist/src/apple/options.d.ts +5 -0
  55. package/dist/src/apple/options.js.map +1 -1
  56. package/dist/src/apple/sentry-swift-package.d.ts +4 -0
  57. package/dist/src/apple/sentry-swift-package.js +17 -0
  58. package/dist/src/apple/sentry-swift-package.js.map +1 -0
  59. package/dist/src/apple/snapshots/apple-snapshots-wizard.d.ts +2 -0
  60. package/dist/src/apple/snapshots/apple-snapshots-wizard.js +251 -0
  61. package/dist/src/apple/snapshots/apple-snapshots-wizard.js.map +1 -0
  62. package/dist/src/apple/snapshots/configure-snapshotpreviews-xcode-project.d.ts +13 -0
  63. package/dist/src/apple/snapshots/configure-snapshotpreviews-xcode-project.js +48 -0
  64. package/dist/src/apple/snapshots/configure-snapshotpreviews-xcode-project.js.map +1 -0
  65. package/dist/src/apple/snapshots/snapshot-test-file.d.ts +18 -0
  66. package/dist/src/apple/snapshots/snapshot-test-file.js +122 -0
  67. package/dist/src/apple/snapshots/snapshot-test-file.js.map +1 -0
  68. package/dist/src/apple/snapshots/snapshot-verification-scheme.d.ts +6 -0
  69. package/dist/src/apple/snapshots/snapshot-verification-scheme.js +147 -0
  70. package/dist/src/apple/snapshots/snapshot-verification-scheme.js.map +1 -0
  71. package/dist/src/apple/snapshots/snapshotpreviews-package.d.ts +4 -0
  72. package/dist/src/apple/snapshots/snapshotpreviews-package.js +8 -0
  73. package/dist/src/apple/snapshots/snapshotpreviews-package.js.map +1 -0
  74. package/dist/src/apple/snapshots/snapshots-cli-preflight.d.ts +23 -0
  75. package/dist/src/apple/snapshots/snapshots-cli-preflight.js +136 -0
  76. package/dist/src/apple/snapshots/snapshots-cli-preflight.js.map +1 -0
  77. package/dist/src/apple/xcode-manager.d.ts +59 -1
  78. package/dist/src/apple/xcode-manager.js +507 -106
  79. package/dist/src/apple/xcode-manager.js.map +1 -1
  80. package/dist/src/cloudflare/cloudflare-wizard.js +5 -0
  81. package/dist/src/cloudflare/cloudflare-wizard.js.map +1 -1
  82. package/dist/src/cloudflare/sdk-setup.d.ts +1 -0
  83. package/dist/src/cloudflare/sdk-setup.js.map +1 -1
  84. package/dist/src/cloudflare/templates.d.ts +1 -0
  85. package/dist/src/cloudflare/templates.js +7 -1
  86. package/dist/src/cloudflare/templates.js.map +1 -1
  87. package/dist/src/cloudflare/wrap-worker.d.ts +1 -0
  88. package/dist/src/cloudflare/wrap-worker.js +7 -0
  89. package/dist/src/cloudflare/wrap-worker.js.map +1 -1
  90. package/dist/src/flutter/flutter-wizard.js +3 -0
  91. package/dist/src/flutter/flutter-wizard.js.map +1 -1
  92. package/dist/src/nextjs/templates.js +12 -6
  93. package/dist/src/nextjs/templates.js.map +1 -1
  94. package/dist/src/nuxt/templates.js +12 -6
  95. package/dist/src/nuxt/templates.js.map +1 -1
  96. package/dist/src/react-native/expo.d.ts +6 -0
  97. package/dist/src/react-native/expo.js +27 -1
  98. package/dist/src/react-native/expo.js.map +1 -1
  99. package/dist/src/react-native/git.d.ts +5 -0
  100. package/dist/src/react-native/git.js +32 -1
  101. package/dist/src/react-native/git.js.map +1 -1
  102. package/dist/src/react-native/javascript.js +3 -1
  103. package/dist/src/react-native/javascript.js.map +1 -1
  104. package/dist/src/react-native/react-native-wizard.js +12 -6
  105. package/dist/src/react-native/react-native-wizard.js.map +1 -1
  106. package/dist/src/react-router/codemods/client.entry.d.ts +1 -1
  107. package/dist/src/react-router/codemods/client.entry.js +124 -26
  108. package/dist/src/react-router/codemods/client.entry.js.map +1 -1
  109. package/dist/src/react-router/codemods/react-router-config.js +1 -1
  110. package/dist/src/react-router/codemods/react-router-config.js.map +1 -1
  111. package/dist/src/react-router/codemods/server-entry.d.ts +1 -1
  112. package/dist/src/react-router/codemods/server-entry.js +40 -4
  113. package/dist/src/react-router/codemods/server-entry.js.map +1 -1
  114. package/dist/src/react-router/codemods/vite.js +46 -1
  115. package/dist/src/react-router/codemods/vite.js.map +1 -1
  116. package/dist/src/react-router/react-router-wizard.js +62 -21
  117. package/dist/src/react-router/react-router-wizard.js.map +1 -1
  118. package/dist/src/react-router/sdk-setup.d.ts +5 -3
  119. package/dist/src/react-router/sdk-setup.js +44 -16
  120. package/dist/src/react-router/sdk-setup.js.map +1 -1
  121. package/dist/src/react-router/templates.d.ts +2 -4
  122. package/dist/src/react-router/templates.js +89 -87
  123. package/dist/src/react-router/templates.js.map +1 -1
  124. package/dist/src/remix/sdk-setup.js +1 -2
  125. package/dist/src/remix/sdk-setup.js.map +1 -1
  126. package/dist/src/run.d.ts +4 -1
  127. package/dist/src/run.js +13 -0
  128. package/dist/src/run.js.map +1 -1
  129. package/dist/src/sourcemaps/tools/remix.js +4 -4
  130. package/dist/src/sourcemaps/tools/remix.js.map +1 -1
  131. package/dist/src/sourcemaps/tools/vite.js +1 -1
  132. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  133. package/dist/src/sveltekit/sdk-setup/setup.js +17 -4
  134. package/dist/src/sveltekit/sdk-setup/setup.js.map +1 -1
  135. package/dist/src/sveltekit/sdk-setup/vite.js +1 -1
  136. package/dist/src/sveltekit/sdk-setup/vite.js.map +1 -1
  137. package/dist/src/sveltekit/templates.js +12 -6
  138. package/dist/src/sveltekit/templates.js.map +1 -1
  139. package/dist/src/utils/ast-utils.d.ts +10 -0
  140. package/dist/src/utils/ast-utils.js +19 -1
  141. package/dist/src/utils/ast-utils.js.map +1 -1
  142. package/dist/src/utils/clack/index.d.ts +2 -1
  143. package/dist/src/utils/clack/index.js +17 -6
  144. package/dist/src/utils/clack/index.js.map +1 -1
  145. package/dist/src/utils/files.d.ts +2 -0
  146. package/dist/src/utils/files.js +58 -0
  147. package/dist/src/utils/files.js.map +1 -0
  148. package/dist/src/utils/git.d.ts +3 -1
  149. package/dist/src/utils/git.js +2 -1
  150. package/dist/src/utils/git.js.map +1 -1
  151. package/dist/src/utils/line-endings.d.ts +1 -0
  152. package/dist/src/utils/line-endings.js +76 -0
  153. package/dist/src/utils/line-endings.js.map +1 -0
  154. package/dist/src/version.d.ts +1 -1
  155. package/dist/src/version.js +1 -1
  156. package/dist/src/version.js.map +1 -1
  157. package/dist/test/angular/angular-wizard.test.js +0 -5
  158. package/dist/test/angular/angular-wizard.test.js.map +1 -1
  159. package/dist/test/apple/code-tools.test.js +78 -0
  160. package/dist/test/apple/code-tools.test.js.map +1 -1
  161. package/dist/test/apple/configure-package-manager.test.d.ts +1 -0
  162. package/dist/test/apple/configure-package-manager.test.js +161 -0
  163. package/dist/test/apple/configure-package-manager.test.js.map +1 -0
  164. package/dist/test/apple/lookup-xcode-project.test.d.ts +1 -0
  165. package/dist/test/apple/lookup-xcode-project.test.js +167 -0
  166. package/dist/test/apple/lookup-xcode-project.test.js.map +1 -0
  167. package/dist/test/apple/snapshots/apple-snapshots-wizard.test.d.ts +1 -0
  168. package/dist/test/apple/snapshots/apple-snapshots-wizard.test.js +487 -0
  169. package/dist/test/apple/snapshots/apple-snapshots-wizard.test.js.map +1 -0
  170. package/dist/test/apple/snapshots/hosted-test-target-fixture.d.ts +24 -0
  171. package/dist/test/apple/snapshots/hosted-test-target-fixture.js +191 -0
  172. package/dist/test/apple/snapshots/hosted-test-target-fixture.js.map +1 -0
  173. package/dist/test/apple/snapshots/snapshot-test-file.test.d.ts +1 -0
  174. package/dist/test/apple/snapshots/snapshot-test-file.test.js +110 -0
  175. package/dist/test/apple/snapshots/snapshot-test-file.test.js.map +1 -0
  176. package/dist/test/apple/snapshots/snapshot-verification-scheme.test.d.ts +1 -0
  177. package/dist/test/apple/snapshots/snapshot-verification-scheme.test.js +146 -0
  178. package/dist/test/apple/snapshots/snapshot-verification-scheme.test.js.map +1 -0
  179. package/dist/test/apple/snapshots/snapshotpreviews-xcode-smoke.test.d.ts +1 -0
  180. package/dist/test/apple/snapshots/snapshotpreviews-xcode-smoke.test.js +186 -0
  181. package/dist/test/apple/snapshots/snapshotpreviews-xcode-smoke.test.js.map +1 -0
  182. package/dist/test/apple/snapshots/snapshots-cli-preflight.test.d.ts +1 -0
  183. package/dist/test/apple/snapshots/snapshots-cli-preflight.test.js +192 -0
  184. package/dist/test/apple/snapshots/snapshots-cli-preflight.test.js.map +1 -0
  185. package/dist/test/apple/snapshots/source-file-insertion.test.d.ts +1 -0
  186. package/dist/test/apple/snapshots/source-file-insertion.test.js +77 -0
  187. package/dist/test/apple/snapshots/source-file-insertion.test.js.map +1 -0
  188. package/dist/test/apple/xcode-manager.test.js +452 -43
  189. package/dist/test/apple/xcode-manager.test.js.map +1 -1
  190. package/dist/test/cloudflare/sdk-setup.test.js +20 -2
  191. package/dist/test/cloudflare/sdk-setup.test.js.map +1 -1
  192. package/dist/test/cloudflare/templates.test.js +54 -0
  193. package/dist/test/cloudflare/templates.test.js.map +1 -1
  194. package/dist/test/cloudflare/wrap-worker.test.js +74 -11
  195. package/dist/test/cloudflare/wrap-worker.test.js.map +1 -1
  196. package/dist/test/constants.test.d.ts +1 -0
  197. package/dist/test/constants.test.js +12 -0
  198. package/dist/test/constants.test.js.map +1 -0
  199. package/dist/test/nextjs/templates.test.js +66 -33
  200. package/dist/test/nextjs/templates.test.js.map +1 -1
  201. package/dist/test/nuxt/templates.test.js +66 -36
  202. package/dist/test/nuxt/templates.test.js.map +1 -1
  203. package/dist/test/react-native/expo.test.js +140 -0
  204. package/dist/test/react-native/expo.test.js.map +1 -1
  205. package/dist/test/react-native/git.test.d.ts +1 -0
  206. package/dist/test/react-native/git.test.js +160 -0
  207. package/dist/test/react-native/git.test.js.map +1 -0
  208. package/dist/test/react-router/codemods/client-entry.test.js +38 -5
  209. package/dist/test/react-router/codemods/client-entry.test.js.map +1 -1
  210. package/dist/test/react-router/codemods/server-entry.test.js +83 -0
  211. package/dist/test/react-router/codemods/server-entry.test.js.map +1 -1
  212. package/dist/test/react-router/codemods/vite.test.js +89 -0
  213. package/dist/test/react-router/codemods/vite.test.js.map +1 -1
  214. package/dist/test/react-router/sdk-setup.test.js +98 -6
  215. package/dist/test/react-router/sdk-setup.test.js.map +1 -1
  216. package/dist/test/react-router/templates.test.js +50 -38
  217. package/dist/test/react-router/templates.test.js.map +1 -1
  218. package/dist/test/remix/build-script.test.d.ts +1 -0
  219. package/dist/test/remix/build-script.test.js +124 -0
  220. package/dist/test/remix/build-script.test.js.map +1 -0
  221. package/dist/test/remix/client-entry.test.js +4 -10
  222. package/dist/test/remix/client-entry.test.js.map +1 -1
  223. package/dist/test/run.test.d.ts +1 -0
  224. package/dist/test/run.test.js +137 -0
  225. package/dist/test/run.test.js.map +1 -0
  226. package/dist/test/sourcemaps/tools/vite.test.js +12 -8
  227. package/dist/test/sourcemaps/tools/vite.test.js.map +1 -1
  228. package/dist/test/sveltekit/templates.test.js +78 -27
  229. package/dist/test/sveltekit/templates.test.js.map +1 -1
  230. package/dist/test/utils/ast-utils.test.js +22 -0
  231. package/dist/test/utils/ast-utils.test.js.map +1 -1
  232. package/dist/test/utils/clack/index.test.js +101 -0
  233. package/dist/test/utils/clack/index.test.js.map +1 -1
  234. package/dist/test/utils/git.test.js +10 -0
  235. package/dist/test/utils/git.test.js.map +1 -1
  236. package/dist/test/utils/line-endings.test.d.ts +1 -0
  237. package/dist/test/utils/line-endings.test.js +103 -0
  238. package/dist/test/utils/line-endings.test.js.map +1 -0
  239. package/package.json +2 -2
  240. package/dist/src/react-router/codemods/root.d.ts +0 -1
  241. package/dist/src/react-router/codemods/root.js +0 -171
  242. package/dist/src/react-router/codemods/root.js.map +0 -1
  243. package/dist/test/react-router/codemods/root.test.js +0 -178
  244. package/dist/test/react-router/codemods/root.test.js.map +0 -1
  245. /package/dist/{test/react-router/codemods/root.test.d.ts → e2e-tests/tests/react-router-instrumentation-api.test.d.ts} +0 -0
@@ -32,10 +32,70 @@ exports.XcodeProject = void 0;
32
32
  const clack = __importStar(require("@clack/prompts"));
33
33
  const fs = __importStar(require("node:fs"));
34
34
  const path = __importStar(require("node:path"));
35
+ const semver_1 = require("semver");
35
36
  const debug_1 = require("../utils/debug");
36
37
  const templates = __importStar(require("./templates"));
37
38
  const xcode_1 = require("xcode");
38
39
  const macos_system_helper_1 = require("./macos-system-helper");
40
+ const XCODE_APPLICATION_PRODUCT_TYPE = 'com.apple.product-type.application';
41
+ const XCODE_UNIT_TEST_PRODUCT_TYPE = 'com.apple.product-type.bundle.unit-test';
42
+ function unquote(value) {
43
+ return typeof value === 'string' ? value.replace(/"/g, '') : '';
44
+ }
45
+ function stripAppExtension(value) {
46
+ return value?.endsWith('.app') ? value.slice(0, -'.app'.length) : value;
47
+ }
48
+ function resolveBuildSettingValue(value, targetName) {
49
+ const resolvedValue = unquote(value)
50
+ .replace(/\$\(TARGET_NAME\)/g, targetName)
51
+ .replace(/\$\{TARGET_NAME\}/g, targetName)
52
+ .trim();
53
+ return resolvedValue && !resolvedValue.includes('$')
54
+ ? resolvedValue
55
+ : undefined;
56
+ }
57
+ function uniqueStrings(values) {
58
+ return [...new Set(values.filter(Boolean))];
59
+ }
60
+ function testHostReferencesApplication(testHost, appHostCandidates) {
61
+ const resolvedTestHost = unquote(testHost);
62
+ if (!resolvedTestHost) {
63
+ return false;
64
+ }
65
+ const referencesAppBundle = appHostCandidates.bundleNames.some((bundleName) => containsPathSegment(resolvedTestHost, `${bundleName}.app`));
66
+ if (!referencesAppBundle) {
67
+ return false;
68
+ }
69
+ return appHostCandidates.executableNames.some((executableName) => containsPathSegment(resolvedTestHost, executableName));
70
+ }
71
+ function containsPathSegment(value, segment) {
72
+ return new RegExp(`(^|/)${escapeRegExp(segment)}(/|$)`).test(value);
73
+ }
74
+ function escapeRegExp(value) {
75
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
76
+ }
77
+ function normalizeSwiftPackageRepositoryURL(value) {
78
+ return unquote(value).replace(/\/+$/, '');
79
+ }
80
+ function shouldUpdatePackageRequirement(existingRequirement, requestedRequirement) {
81
+ if (!existingRequirement || typeof existingRequirement !== 'object') {
82
+ return true;
83
+ }
84
+ const requirement = existingRequirement;
85
+ if (requirement.kind !== requestedRequirement.kind) {
86
+ return true;
87
+ }
88
+ const existingMinimumVersion = requirement.minimumVersion;
89
+ if (typeof existingMinimumVersion !== 'string') {
90
+ return true;
91
+ }
92
+ const existingVersion = (0, semver_1.valid)(existingMinimumVersion);
93
+ const requestedVersion = (0, semver_1.valid)(requestedRequirement.minimumVersion);
94
+ if (!existingVersion || !requestedVersion) {
95
+ return existingMinimumVersion !== requestedRequirement.minimumVersion;
96
+ }
97
+ return (0, semver_1.lt)(existingVersion, requestedVersion);
98
+ }
39
99
  function setDebugInformationFormatAndSandbox(proj, targetName) {
40
100
  const xcObjects = proj.hash.project.objects;
41
101
  if (!xcObjects.PBXNativeTarget) {
@@ -70,106 +130,6 @@ function setDebugInformationFormatAndSandbox(proj, targetName) {
70
130
  xcObjects.XCBuildConfiguration[buildListConfigId.value] = config;
71
131
  }
72
132
  }
73
- function addSentrySPM(proj, targetName) {
74
- const xcObjects = proj.hash.project.objects;
75
- const sentryFrameworkUUID = proj.generateUuid();
76
- const sentrySPMUUID = proj.generateUuid();
77
- // Check whether xcObjects already have sentry framework
78
- if (xcObjects.PBXFrameworksBuildPhase) {
79
- for (const key in xcObjects.PBXFrameworksBuildPhase || {}) {
80
- const frameworkBuildPhase = xcObjects.PBXFrameworksBuildPhase[key];
81
- if (key.endsWith('_comment') || typeof frameworkBuildPhase === 'string') {
82
- // Ignore comments
83
- continue;
84
- }
85
- for (const framework of frameworkBuildPhase.files ?? []) {
86
- // We identify the Sentry framework by the comment "Sentry in Frameworks",
87
- // which is set by this manager in previous runs.
88
- if (framework.comment === 'Sentry in Frameworks') {
89
- return;
90
- }
91
- }
92
- }
93
- }
94
- if (!xcObjects.PBXBuildFile) {
95
- xcObjects.PBXBuildFile = {};
96
- }
97
- xcObjects.PBXBuildFile[sentryFrameworkUUID] = {
98
- isa: 'PBXBuildFile',
99
- productRef: sentrySPMUUID,
100
- productRef_comment: 'Sentry',
101
- };
102
- xcObjects.PBXBuildFile[`${sentryFrameworkUUID}_comment`] =
103
- 'Sentry in Frameworks';
104
- if (!xcObjects.PBXFrameworksBuildPhase) {
105
- xcObjects.PBXFrameworksBuildPhase = {};
106
- }
107
- for (const key in xcObjects.PBXFrameworksBuildPhase) {
108
- const value = xcObjects.PBXFrameworksBuildPhase[key];
109
- if (key.endsWith('_comment') || typeof value === 'string') {
110
- // Ignore comments
111
- continue;
112
- }
113
- const frameworks = value.files ?? [];
114
- frameworks.push({
115
- value: sentryFrameworkUUID,
116
- comment: 'Sentry in Frameworks',
117
- });
118
- value.files = frameworks;
119
- xcObjects.PBXFrameworksBuildPhase[key] = value;
120
- }
121
- if (!xcObjects.PBXNativeTarget) {
122
- xcObjects.PBXNativeTarget = {};
123
- }
124
- const targetKey = Object.keys(xcObjects.PBXNativeTarget || {}).filter((key) => {
125
- const value = xcObjects.PBXNativeTarget?.[key];
126
- return (!key.endsWith('_comment') &&
127
- typeof value !== 'string' &&
128
- value?.name === targetName);
129
- })[0];
130
- const target = xcObjects.PBXNativeTarget[targetKey];
131
- if (!target.packageProductDependencies) {
132
- target.packageProductDependencies = [];
133
- }
134
- target.packageProductDependencies.push({
135
- value: sentrySPMUUID,
136
- comment: 'Sentry',
137
- });
138
- const sentrySwiftPackageUUID = proj.generateUuid();
139
- const xcProject = proj.getFirstProject().firstProject;
140
- if (!xcProject.packageReferences) {
141
- xcProject.packageReferences = [];
142
- }
143
- xcProject.packageReferences.push({
144
- value: sentrySwiftPackageUUID,
145
- comment: 'XCRemoteSwiftPackageReference "sentry-cocoa"',
146
- });
147
- if (!xcObjects.XCRemoteSwiftPackageReference) {
148
- xcObjects.XCRemoteSwiftPackageReference = {};
149
- }
150
- xcObjects.XCRemoteSwiftPackageReference[sentrySwiftPackageUUID] = {
151
- isa: 'XCRemoteSwiftPackageReference',
152
- repositoryURL: '"https://github.com/getsentry/sentry-cocoa/"',
153
- requirement: {
154
- kind: 'upToNextMajorVersion',
155
- minimumVersion: '8.0.0',
156
- },
157
- };
158
- xcObjects.XCRemoteSwiftPackageReference[`${sentrySwiftPackageUUID}_comment`] =
159
- 'XCRemoteSwiftPackageReference "sentry-cocoa"';
160
- if (!xcObjects.XCSwiftPackageProductDependency) {
161
- xcObjects.XCSwiftPackageProductDependency = {};
162
- }
163
- xcObjects.XCSwiftPackageProductDependency[sentrySPMUUID] = {
164
- isa: 'XCSwiftPackageProductDependency',
165
- package: sentrySwiftPackageUUID,
166
- package_comment: 'XCRemoteSwiftPackageReference "sentry-cocoa"',
167
- productName: 'Sentry',
168
- };
169
- xcObjects.XCSwiftPackageProductDependency[`${sentrySPMUUID}_comment`] =
170
- 'Sentry';
171
- clack.log.step('Added Sentry SPM dependency to your project');
172
- }
173
133
  class XcodeProject {
174
134
  /**
175
135
  * The directory where the Xcode project is located.
@@ -208,13 +168,110 @@ class XcodeProject {
208
168
  const value = targets[key];
209
169
  return (!key.endsWith('_comment') &&
210
170
  typeof value !== 'string' &&
211
- value.productType.startsWith('"com.apple.product-type.application'));
171
+ unquote(value.productType).startsWith(XCODE_APPLICATION_PRODUCT_TYPE));
172
+ })
173
+ .map((key) => {
174
+ return targets[key].name;
175
+ });
176
+ }
177
+ getUnitTestTargetNames() {
178
+ const targets = this.objects.PBXNativeTarget ?? {};
179
+ return Object.keys(targets)
180
+ .filter((key) => {
181
+ const value = targets[key];
182
+ return this.isUnitTestTargetEntry(key, value);
212
183
  })
213
184
  .map((key) => {
214
185
  return targets[key].name;
215
186
  });
216
187
  }
217
- updateXcodeProject(sentryProject, target, addSPMReference, uploadSource = true) {
188
+ getHostedUnitTestTargetNames() {
189
+ const targets = this.objects.PBXNativeTarget ?? {};
190
+ return Object.keys(targets)
191
+ .filter((key) => {
192
+ const value = targets[key];
193
+ return (this.isUnitTestTargetEntry(key, value) &&
194
+ this.getTargetBuildSettings(value).some((buildSettings) => Boolean(unquote(buildSettings.TEST_HOST).trim())));
195
+ })
196
+ .map((key) => {
197
+ return targets[key].name;
198
+ });
199
+ }
200
+ getHostedUnitTestTargetNamesForApplicationTarget(appTargetName) {
201
+ const appTarget = this.findNativeTargetByName(appTargetName);
202
+ if (!appTarget) {
203
+ return [];
204
+ }
205
+ const appHostCandidates = this.getApplicationHostCandidates(appTarget);
206
+ const targets = this.objects.PBXNativeTarget ?? {};
207
+ return Object.keys(targets)
208
+ .filter((key) => {
209
+ const value = targets[key];
210
+ return (!key.endsWith('_comment') &&
211
+ typeof value !== 'string' &&
212
+ unquote(value.productType) === XCODE_UNIT_TEST_PRODUCT_TYPE &&
213
+ this.getTargetBuildSettings(value).some((buildSettings) => testHostReferencesApplication(buildSettings.TEST_HOST, appHostCandidates)));
214
+ })
215
+ .map((key) => {
216
+ return targets[key].name;
217
+ });
218
+ }
219
+ getBundleIdentifierForTarget(targetName) {
220
+ const target = this.findNativeTargetByName(targetName);
221
+ if (!target) {
222
+ return undefined;
223
+ }
224
+ return this.getTargetBuildSettings(target.obj)
225
+ .map((buildSettings) => {
226
+ return unquote(buildSettings.PRODUCT_BUNDLE_IDENTIFIER);
227
+ })
228
+ .find(Boolean);
229
+ }
230
+ /**
231
+ * Idempotently links a Swift package product to one target dependency list
232
+ * and Frameworks build phase. Returns whether the pbxproj graph changed and
233
+ * whether the product is linked after the operation.
234
+ */
235
+ ensureSwiftPackageProductLinked(targetName, product) {
236
+ const target = this.findNativeTargetByName(targetName);
237
+ if (!target) {
238
+ (0, debug_1.debug)(`Target not found: ${targetName}`);
239
+ return { changed: false, linked: false };
240
+ }
241
+ const frameworksBuildPhase = this.findFrameworksBuildPhaseInTarget(target.obj);
242
+ if (!frameworksBuildPhase) {
243
+ (0, debug_1.debug)(`Frameworks build phase not found for target: ${targetName}`);
244
+ return { changed: false, linked: false };
245
+ }
246
+ let changed = false;
247
+ // Ensure the remote Swift package object exists.
248
+ const packageReference = this.ensureSwiftPackageReference(product.package);
249
+ changed = packageReference.changed || changed;
250
+ // Attach the Swift package object to the root Xcode project.
251
+ changed =
252
+ this.ensureProjectSwiftPackageReference(packageReference.packageRefId, product.package.commentName) || changed;
253
+ // Ensure the package product dependency object exists.
254
+ const productDependency = this.ensureSwiftPackageProductDependency(packageReference.packageRefId, product);
255
+ changed = productDependency.changed || changed;
256
+ if (!target.obj.packageProductDependencies) {
257
+ target.obj.packageProductDependencies = [];
258
+ }
259
+ // Attach the package product dependency to the selected target.
260
+ if (!target.obj.packageProductDependencies.some((dependency) => {
261
+ return dependency.value === productDependency.productDependencyId;
262
+ })) {
263
+ target.obj.packageProductDependencies.push({
264
+ value: productDependency.productDependencyId,
265
+ comment: product.productName,
266
+ });
267
+ changed = true;
268
+ }
269
+ // Link the package product in the target Frameworks build phase.
270
+ changed =
271
+ this.ensureFrameworksBuildFile(frameworksBuildPhase, productDependency.productDependencyId, product) || changed;
272
+ return { changed, linked: true };
273
+ }
274
+ updateXcodeProject(sentryProject, target, swiftPackageProduct, uploadSource = true) {
218
275
  this.addUploadSymbolsScript({
219
276
  sentryProject,
220
277
  targetName: target,
@@ -223,11 +280,15 @@ class XcodeProject {
223
280
  if (uploadSource) {
224
281
  setDebugInformationFormatAndSandbox(this.project, target);
225
282
  }
226
- if (addSPMReference) {
227
- addSentrySPM(this.project, target);
283
+ if (swiftPackageProduct &&
284
+ !(swiftPackageProduct.existingFrameworkComment &&
285
+ this.hasFrameworkBuildFileCommentInTarget(target, swiftPackageProduct.existingFrameworkComment))) {
286
+ const result = this.ensureSwiftPackageProductLinked(target, swiftPackageProduct.product);
287
+ if (result.changed && swiftPackageProduct.successMessage) {
288
+ clack.log.step(swiftPackageProduct.successMessage);
289
+ }
228
290
  }
229
- const newContent = this.project.writeSync();
230
- fs.writeFileSync(this.pbxprojPath, newContent);
291
+ this.write();
231
292
  }
232
293
  addUploadSymbolsScript({ sentryProject, targetName, uploadSource, }) {
233
294
  const xcObjects = this.project.hash.project.objects;
@@ -306,6 +367,87 @@ class XcodeProject {
306
367
  clack.log.step(`Added Sentry upload script to "${targetName}" build phase`);
307
368
  }
308
369
  }
370
+ write() {
371
+ const newContent = this.project.writeSync();
372
+ fs.writeFileSync(this.pbxprojPath, newContent);
373
+ }
374
+ getSynchronizedRootGroupPathsForTarget(targetName) {
375
+ const nativeTarget = this.findNativeTargetByName(targetName);
376
+ if (!nativeTarget) {
377
+ return [];
378
+ }
379
+ return (nativeTarget.obj.fileSystemSynchronizedGroups ?? []).reduce((groupPaths, group) => {
380
+ const groupObj = this.objects.PBXFileSystemSynchronizedRootGroup?.[group.value];
381
+ if (!groupObj || typeof groupObj !== 'object') {
382
+ return groupPaths;
383
+ }
384
+ const groupPath = this.resolveAbsolutePathOfSynchronizedRootGroup({
385
+ id: group.value,
386
+ obj: groupObj,
387
+ });
388
+ if (!groupPath) {
389
+ return groupPaths;
390
+ }
391
+ return groupPaths.concat(groupPath);
392
+ }, new Array());
393
+ }
394
+ /**
395
+ * Ensures a Swift file is compiled by a target, either through an Xcode
396
+ * synchronized root group or an explicit Sources build phase entry.
397
+ */
398
+ addSwiftSourceFileToTarget(args) {
399
+ const nativeTarget = this.findNativeTargetByName(args.targetName);
400
+ const defaultResult = { changed: false, included: false };
401
+ if (!nativeTarget) {
402
+ (0, debug_1.debug)(`Target not found: ${args.targetName}`);
403
+ return defaultResult;
404
+ }
405
+ const absoluteFilePath = args.filePath;
406
+ if (this.isFileIncludedBySynchronizedRootGroup(nativeTarget, absoluteFilePath)) {
407
+ return {
408
+ changed: false,
409
+ included: true,
410
+ };
411
+ }
412
+ const sourceBuildPhase = this.findSourceBuildPhaseInTarget(nativeTarget.obj);
413
+ if (!sourceBuildPhase) {
414
+ (0, debug_1.debug)(`Sources build phase not found for target: ${args.targetName}`);
415
+ return defaultResult;
416
+ }
417
+ const fileReference = this.ensureSwiftFileReference(absoluteFilePath);
418
+ const sourceBuildPhaseFiles = sourceBuildPhase.obj.files ?? [];
419
+ const existingBuildFileReference = sourceBuildPhaseFiles.find((file) => {
420
+ const buildFile = this.objects.PBXBuildFile?.[file.value];
421
+ return (buildFile &&
422
+ typeof buildFile !== 'string' &&
423
+ buildFile.fileRef === fileReference.fileReferenceId);
424
+ });
425
+ if (existingBuildFileReference) {
426
+ return {
427
+ changed: fileReference.changed,
428
+ included: true,
429
+ };
430
+ }
431
+ if (!this.objects.PBXBuildFile) {
432
+ this.objects.PBXBuildFile = {};
433
+ }
434
+ const buildFileId = this.project.generateUuid();
435
+ const fileName = path.basename(absoluteFilePath);
436
+ this.objects.PBXBuildFile[buildFileId] = {
437
+ isa: 'PBXBuildFile',
438
+ fileRef: fileReference.fileReferenceId,
439
+ fileRef_comment: fileName,
440
+ };
441
+ this.objects.PBXBuildFile[`${buildFileId}_comment`] = `${fileName} in Sources`;
442
+ sourceBuildPhase.obj.files = [
443
+ ...sourceBuildPhaseFiles,
444
+ {
445
+ value: buildFileId,
446
+ comment: `${fileName} in Sources`,
447
+ },
448
+ ];
449
+ return { changed: true, included: true };
450
+ }
309
451
  /**
310
452
  * Retrieves all source files associated with a specific target in the Xcode project.
311
453
  * This is used to find files where we can inject Sentry initialization code.
@@ -343,6 +485,265 @@ class XcodeProject {
343
485
  (0, debug_1.debug)(`Found ${filesInSynchronizedRootGroups.length} files in synchronized root groups for target: ${targetName}`);
344
486
  return [...filesInBuildPhase, ...filesInSynchronizedRootGroups];
345
487
  }
488
+ isUnitTestTargetEntry(key, value) {
489
+ return (!key.endsWith('_comment') &&
490
+ typeof value !== 'string' &&
491
+ value !== undefined &&
492
+ unquote(value.productType) === XCODE_UNIT_TEST_PRODUCT_TYPE);
493
+ }
494
+ getTargetBuildSettings(target) {
495
+ const buildConfigurationListId = target.buildConfigurationList;
496
+ if (!buildConfigurationListId) {
497
+ return [];
498
+ }
499
+ const configurationList = this.objects.XCConfigurationList?.[buildConfigurationListId];
500
+ if (!configurationList || typeof configurationList === 'string') {
501
+ return [];
502
+ }
503
+ return (configurationList.buildConfigurations ?? []).reduce((buildSettings, buildConfiguration) => {
504
+ const configuration = this.objects.XCBuildConfiguration?.[buildConfiguration.value];
505
+ if (!configuration || typeof configuration === 'string') {
506
+ return buildSettings;
507
+ }
508
+ return buildSettings.concat(configuration.buildSettings ?? {});
509
+ }, new Array());
510
+ }
511
+ getApplicationHostCandidates(target) {
512
+ const buildSettings = this.getTargetBuildSettings(target.obj);
513
+ const productReferencePath = this.getProductReferencePath(target.obj);
514
+ const productReferenceName = stripAppExtension(productReferencePath);
515
+ const targetProductName = resolveBuildSettingValue(target.obj.productName, target.obj.name);
516
+ const buildSettingProductNames = buildSettings.flatMap((settings) => [
517
+ resolveBuildSettingValue(settings.PRODUCT_NAME, target.obj.name),
518
+ stripAppExtension(resolveBuildSettingValue(settings.FULL_PRODUCT_NAME, target.obj.name)),
519
+ stripAppExtension(resolveBuildSettingValue(settings.WRAPPER_NAME, target.obj.name)),
520
+ ]);
521
+ const buildSettingExecutableNames = buildSettings.map((settings) => resolveBuildSettingValue(settings.EXECUTABLE_NAME, target.obj.name));
522
+ return {
523
+ bundleNames: uniqueStrings([
524
+ target.obj.name,
525
+ targetProductName,
526
+ productReferenceName,
527
+ ...buildSettingProductNames,
528
+ ]),
529
+ executableNames: uniqueStrings([
530
+ target.obj.name,
531
+ targetProductName,
532
+ productReferenceName,
533
+ ...buildSettingExecutableNames,
534
+ ]),
535
+ };
536
+ }
537
+ getProductReferencePath(target) {
538
+ if (!target.productReference) {
539
+ return undefined;
540
+ }
541
+ const productReference = this.objects.PBXFileReference?.[target.productReference];
542
+ if (!productReference || typeof productReference === 'string') {
543
+ return undefined;
544
+ }
545
+ return unquote(productReference.path);
546
+ }
547
+ hasFrameworkBuildFileCommentInTarget(targetName, comment) {
548
+ const target = this.findNativeTargetByName(targetName);
549
+ if (!target) {
550
+ return false;
551
+ }
552
+ const frameworksBuildPhase = this.findFrameworksBuildPhaseInTarget(target.obj);
553
+ return (frameworksBuildPhase?.files ?? []).some((framework) => {
554
+ return framework.comment === comment;
555
+ });
556
+ }
557
+ ensureSwiftPackageReference(packageSpec) {
558
+ if (!this.objects.XCRemoteSwiftPackageReference) {
559
+ this.objects.XCRemoteSwiftPackageReference = {};
560
+ }
561
+ const packageReferences = this.objects
562
+ .XCRemoteSwiftPackageReference;
563
+ const requestedRepositoryURL = normalizeSwiftPackageRepositoryURL(packageSpec.repositoryURL);
564
+ const existingPackageReference = Object.entries(packageReferences).find(([id, value]) => {
565
+ if (id.endsWith('_comment') || typeof value !== 'object') {
566
+ return false;
567
+ }
568
+ const packageReference = value;
569
+ return (normalizeSwiftPackageRepositoryURL(packageReference.repositoryURL) ===
570
+ requestedRepositoryURL);
571
+ });
572
+ if (existingPackageReference) {
573
+ const packageReference = existingPackageReference[1];
574
+ if (shouldUpdatePackageRequirement(packageReference.requirement, packageSpec.requirement)) {
575
+ packageReference.requirement = packageSpec.requirement;
576
+ return { packageRefId: existingPackageReference[0], changed: true };
577
+ }
578
+ return { packageRefId: existingPackageReference[0], changed: false };
579
+ }
580
+ const packageRefId = this.project.generateUuid();
581
+ packageReferences[packageRefId] = {
582
+ isa: 'XCRemoteSwiftPackageReference',
583
+ repositoryURL: `"${packageSpec.repositoryURL}"`,
584
+ requirement: packageSpec.requirement,
585
+ };
586
+ packageReferences[`${packageRefId}_comment`] =
587
+ this.swiftPackageReferenceComment(packageSpec.commentName);
588
+ return { packageRefId, changed: true };
589
+ }
590
+ ensureProjectSwiftPackageReference(packageRefId, commentName) {
591
+ const xcodeProject = this.project.getFirstProject().firstProject;
592
+ if (!xcodeProject.packageReferences) {
593
+ xcodeProject.packageReferences = [];
594
+ }
595
+ if (xcodeProject.packageReferences.some((packageReference) => {
596
+ return packageReference.value === packageRefId;
597
+ })) {
598
+ return false;
599
+ }
600
+ xcodeProject.packageReferences.push({
601
+ value: packageRefId,
602
+ comment: this.swiftPackageReferenceComment(commentName),
603
+ });
604
+ return true;
605
+ }
606
+ ensureSwiftPackageProductDependency(packageRefId, product) {
607
+ if (!this.objects.XCSwiftPackageProductDependency) {
608
+ this.objects.XCSwiftPackageProductDependency = {};
609
+ }
610
+ const productDependencies = this.objects
611
+ .XCSwiftPackageProductDependency;
612
+ const existingProductDependency = Object.entries(productDependencies).find(([id, value]) => {
613
+ if (id.endsWith('_comment') || typeof value !== 'object') {
614
+ return false;
615
+ }
616
+ const productDependency = value;
617
+ return (productDependency.package === packageRefId &&
618
+ unquote(productDependency.productName) === product.productName);
619
+ });
620
+ if (existingProductDependency) {
621
+ return {
622
+ productDependencyId: existingProductDependency[0],
623
+ changed: false,
624
+ };
625
+ }
626
+ const productDependencyId = this.project.generateUuid();
627
+ productDependencies[productDependencyId] = {
628
+ isa: 'XCSwiftPackageProductDependency',
629
+ package: packageRefId,
630
+ package_comment: this.swiftPackageReferenceComment(product.package.commentName),
631
+ productName: product.productName,
632
+ };
633
+ productDependencies[`${productDependencyId}_comment`] = product.productName;
634
+ return { productDependencyId, changed: true };
635
+ }
636
+ ensureFrameworksBuildFile(frameworksBuildPhase, productDependencyId, product) {
637
+ if (!this.objects.PBXBuildFile) {
638
+ this.objects.PBXBuildFile = {};
639
+ }
640
+ const existingFrameworkEntry = (frameworksBuildPhase.files ?? []).find((framework) => {
641
+ const buildFile = this.objects.PBXBuildFile?.[framework.value];
642
+ return (buildFile &&
643
+ typeof buildFile !== 'string' &&
644
+ buildFile.productRef === productDependencyId);
645
+ });
646
+ if (existingFrameworkEntry) {
647
+ return false;
648
+ }
649
+ const buildFileId = this.project.generateUuid();
650
+ this.objects.PBXBuildFile[buildFileId] = {
651
+ isa: 'PBXBuildFile',
652
+ productRef: productDependencyId,
653
+ productRef_comment: product.productName,
654
+ };
655
+ this.objects.PBXBuildFile[`${buildFileId}_comment`] = `${product.productName} in Frameworks`;
656
+ if (!frameworksBuildPhase.files) {
657
+ frameworksBuildPhase.files = [];
658
+ }
659
+ frameworksBuildPhase.files.push({
660
+ value: buildFileId,
661
+ comment: `${product.productName} in Frameworks`,
662
+ });
663
+ return true;
664
+ }
665
+ findFrameworksBuildPhaseInTarget(target) {
666
+ for (const buildPhaseReference of target.buildPhases ?? []) {
667
+ const buildPhase = this.objects.PBXFrameworksBuildPhase?.[buildPhaseReference.value];
668
+ if (buildPhase && typeof buildPhase !== 'string') {
669
+ return buildPhase;
670
+ }
671
+ }
672
+ return undefined;
673
+ }
674
+ swiftPackageReferenceComment(commentName) {
675
+ return `XCRemoteSwiftPackageReference "${commentName}"`;
676
+ }
677
+ isFileIncludedBySynchronizedRootGroup(nativeTarget, absoluteFilePath) {
678
+ return this.findFilesInSynchronizedRootGroups(nativeTarget).some((filePath) => filePath === absoluteFilePath);
679
+ }
680
+ ensureSwiftFileReference(absoluteFilePath) {
681
+ if (!this.objects.PBXFileReference) {
682
+ this.objects.PBXFileReference = {};
683
+ }
684
+ const existingFileReference = Object.entries(this.objects.PBXFileReference).find(([id, fileReference]) => {
685
+ if (id.endsWith('_comment') || typeof fileReference !== 'object') {
686
+ return false;
687
+ }
688
+ const resolvedFilePath = this.resolveAbsolutePathOfFileReference({
689
+ id,
690
+ obj: fileReference,
691
+ });
692
+ return resolvedFilePath === absoluteFilePath;
693
+ });
694
+ if (existingFileReference) {
695
+ return { fileReferenceId: existingFileReference[0], changed: false };
696
+ }
697
+ const fileReferenceId = this.project.generateUuid();
698
+ const fileName = path.basename(absoluteFilePath);
699
+ const relativePath = path.relative(this.baseDir, absoluteFilePath);
700
+ this.objects.PBXFileReference[fileReferenceId] = {
701
+ isa: 'PBXFileReference',
702
+ lastKnownFileType: 'sourcecode.swift',
703
+ path: relativePath,
704
+ sourceTree: 'SOURCE_ROOT',
705
+ };
706
+ this.objects.PBXFileReference[`${fileReferenceId}_comment`] = fileName;
707
+ this.addFileReferenceToBestGroup(fileReferenceId, fileName, absoluteFilePath);
708
+ return { fileReferenceId, changed: true };
709
+ }
710
+ addFileReferenceToBestGroup(fileReferenceId, fileName, absoluteFilePath) {
711
+ const parentDirectory = path.dirname(absoluteFilePath);
712
+ const parentGroup = this.groups.find((group) => {
713
+ const groupPath = this.resolveAbsolutePathOfGroup(group);
714
+ return groupPath === parentDirectory;
715
+ }) ?? this.mainGroup;
716
+ if (!parentGroup) {
717
+ return;
718
+ }
719
+ if (!parentGroup.obj.children) {
720
+ parentGroup.obj.children = [];
721
+ }
722
+ if (parentGroup.obj.children.some((child) => child.value === fileReferenceId)) {
723
+ return;
724
+ }
725
+ parentGroup.obj.children.push({
726
+ value: fileReferenceId,
727
+ comment: fileName,
728
+ });
729
+ }
730
+ get mainGroup() {
731
+ const project = Object.entries(this.objects.PBXProject ?? {}).find(([id, candidate]) => {
732
+ return !id.endsWith('_comment') && typeof candidate === 'object';
733
+ })?.[1];
734
+ if (!project || typeof project !== 'object') {
735
+ return undefined;
736
+ }
737
+ const mainGroupId = project.mainGroup;
738
+ if (!mainGroupId) {
739
+ return undefined;
740
+ }
741
+ const mainGroup = this.objects.PBXGroup?.[mainGroupId];
742
+ if (!mainGroup || typeof mainGroup !== 'object') {
743
+ return undefined;
744
+ }
745
+ return { id: mainGroupId, obj: mainGroup };
746
+ }
346
747
  // ================================ TARGET HELPERS ================================
347
748
  /**
348
749
  * Finds a native target by name.