@sentry/wizard 4.7.0 → 4.9.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 (175) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +19 -19
  3. package/dist/e2e-tests/tests/angular-17.test.d.ts +1 -0
  4. package/dist/e2e-tests/tests/angular-17.test.js +196 -0
  5. package/dist/e2e-tests/tests/angular-17.test.js.map +1 -0
  6. package/dist/e2e-tests/tests/angular-19.test.d.ts +1 -0
  7. package/dist/e2e-tests/tests/angular-19.test.js +194 -0
  8. package/dist/e2e-tests/tests/angular-19.test.js.map +1 -0
  9. package/dist/e2e-tests/tests/expo.test.d.ts +1 -0
  10. package/dist/e2e-tests/tests/expo.test.js +103 -0
  11. package/dist/e2e-tests/tests/expo.test.js.map +1 -0
  12. package/dist/e2e-tests/tests/help-message.test.js +2 -2
  13. package/dist/e2e-tests/tests/help-message.test.js.map +1 -1
  14. package/dist/e2e-tests/tests/react-native.test.d.ts +1 -0
  15. package/dist/e2e-tests/tests/react-native.test.js +132 -0
  16. package/dist/e2e-tests/tests/react-native.test.js.map +1 -0
  17. package/dist/e2e-tests/tests/remix.test.js +4 -4
  18. package/dist/e2e-tests/tests/remix.test.js.map +1 -1
  19. package/dist/e2e-tests/tests/sveltekit.test.js +2 -2
  20. package/dist/e2e-tests/tests/sveltekit.test.js.map +1 -1
  21. package/dist/e2e-tests/utils/index.d.ts +7 -0
  22. package/dist/e2e-tests/utils/index.js +32 -7
  23. package/dist/e2e-tests/utils/index.js.map +1 -1
  24. package/dist/lib/Constants.d.ts +1 -0
  25. package/dist/lib/Constants.js +3 -0
  26. package/dist/lib/Constants.js.map +1 -1
  27. package/dist/lib/Helper/SentryCli.d.ts +0 -11
  28. package/dist/lib/Helper/SentryCli.js +0 -52
  29. package/dist/lib/Helper/SentryCli.js.map +1 -1
  30. package/dist/src/android/templates.js +2 -0
  31. package/dist/src/android/templates.js.map +1 -1
  32. package/dist/src/angular/angular-wizard.d.ts +3 -0
  33. package/dist/src/angular/angular-wizard.js +186 -0
  34. package/dist/src/angular/angular-wizard.js.map +1 -0
  35. package/dist/src/angular/codemods/app-config.d.ts +3 -0
  36. package/dist/src/angular/codemods/app-config.js +211 -0
  37. package/dist/src/angular/codemods/app-config.js.map +1 -0
  38. package/dist/src/angular/codemods/main.d.ts +20 -0
  39. package/dist/src/angular/codemods/main.js +62 -0
  40. package/dist/src/angular/codemods/main.js.map +1 -0
  41. package/dist/src/angular/codemods/sourcemaps.d.ts +21 -0
  42. package/dist/src/angular/codemods/sourcemaps.js +94 -0
  43. package/dist/src/angular/codemods/sourcemaps.js.map +1 -0
  44. package/dist/src/angular/example-component.d.ts +8 -0
  45. package/dist/src/angular/example-component.js +286 -0
  46. package/dist/src/angular/example-component.js.map +1 -0
  47. package/dist/src/angular/sdk-setup.d.ts +6 -0
  48. package/dist/src/angular/sdk-setup.js +99 -0
  49. package/dist/src/angular/sdk-setup.js.map +1 -0
  50. package/dist/src/apple/code-tools.d.ts +4 -2
  51. package/dist/src/apple/code-tools.js +21 -11
  52. package/dist/src/apple/code-tools.js.map +1 -1
  53. package/dist/src/apple/inject-code-snippet.js +5 -3
  54. package/dist/src/apple/inject-code-snippet.js.map +1 -1
  55. package/dist/src/apple/macos-system-helper.d.ts +5 -0
  56. package/dist/src/apple/macos-system-helper.js +86 -0
  57. package/dist/src/apple/macos-system-helper.js.map +1 -0
  58. package/dist/src/apple/templates.js +10 -0
  59. package/dist/src/apple/templates.js.map +1 -1
  60. package/dist/src/apple/xcode-manager.d.ts +237 -11
  61. package/dist/src/apple/xcode-manager.js +736 -65
  62. package/dist/src/apple/xcode-manager.js.map +1 -1
  63. package/dist/src/apple/xcode-project-object-with-id.d.ts +5 -0
  64. package/dist/src/apple/xcode-project-object-with-id.js +3 -0
  65. package/dist/src/apple/xcode-project-object-with-id.js.map +1 -0
  66. package/dist/src/flutter/flutter-wizard.js +10 -2
  67. package/dist/src/flutter/flutter-wizard.js.map +1 -1
  68. package/dist/src/flutter/templates.js +7 -1
  69. package/dist/src/flutter/templates.js.map +1 -1
  70. package/dist/src/nextjs/nextjs-wizard.js +27 -15
  71. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  72. package/dist/src/nextjs/templates.js +56 -7
  73. package/dist/src/nextjs/templates.js.map +1 -1
  74. package/dist/src/nuxt/nuxt-wizard.js +1 -3
  75. package/dist/src/nuxt/nuxt-wizard.js.map +1 -1
  76. package/dist/src/nuxt/templates.js +30 -0
  77. package/dist/src/nuxt/templates.js.map +1 -1
  78. package/dist/src/react-native/expo-env-file.js +5 -0
  79. package/dist/src/react-native/expo-env-file.js.map +1 -1
  80. package/dist/src/react-native/expo-metro.js +22 -6
  81. package/dist/src/react-native/expo-metro.js.map +1 -1
  82. package/dist/src/react-native/expo.js +11 -1
  83. package/dist/src/react-native/expo.js.map +1 -1
  84. package/dist/src/react-native/glob.js +14 -4
  85. package/dist/src/react-native/glob.js.map +1 -1
  86. package/dist/src/react-native/gradle.js +14 -4
  87. package/dist/src/react-native/gradle.js.map +1 -1
  88. package/dist/src/react-native/javascript.d.ts +9 -4
  89. package/dist/src/react-native/javascript.js +52 -23
  90. package/dist/src/react-native/javascript.js.map +1 -1
  91. package/dist/src/react-native/metro.d.ts +1 -1
  92. package/dist/src/react-native/metro.js +36 -4
  93. package/dist/src/react-native/metro.js.map +1 -1
  94. package/dist/src/react-native/react-native-wizard.d.ts +4 -0
  95. package/dist/src/react-native/react-native-wizard.js +32 -4
  96. package/dist/src/react-native/react-native-wizard.js.map +1 -1
  97. package/dist/src/react-native/xcode.js +29 -10
  98. package/dist/src/react-native/xcode.js.map +1 -1
  99. package/dist/src/remix/remix-wizard.js +1 -3
  100. package/dist/src/remix/remix-wizard.js.map +1 -1
  101. package/dist/src/remix/sdk-example.js +30 -1
  102. package/dist/src/remix/sdk-example.js.map +1 -1
  103. package/dist/src/remix/sdk-setup.js +11 -5
  104. package/dist/src/remix/sdk-setup.js.map +1 -1
  105. package/dist/src/run.d.ts +1 -1
  106. package/dist/src/run.js +5 -0
  107. package/dist/src/run.js.map +1 -1
  108. package/dist/src/sourcemaps/sourcemaps-wizard.d.ts +1 -1
  109. package/dist/src/sourcemaps/sourcemaps-wizard.js +35 -20
  110. package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
  111. package/dist/src/sourcemaps/tools/angular.d.ts +1 -0
  112. package/dist/src/sourcemaps/tools/angular.js +7 -7
  113. package/dist/src/sourcemaps/tools/angular.js.map +1 -1
  114. package/dist/src/sourcemaps/tools/sentry-cli.d.ts +5 -1
  115. package/dist/src/sourcemaps/tools/sentry-cli.js +6 -3
  116. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  117. package/dist/src/sourcemaps/tools/tsc.js +5 -1
  118. package/dist/src/sourcemaps/tools/tsc.js.map +1 -1
  119. package/dist/src/sourcemaps/tools/vite.js +4 -1
  120. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  121. package/dist/src/sourcemaps/tools/webpack.js +4 -1
  122. package/dist/src/sourcemaps/tools/webpack.js.map +1 -1
  123. package/dist/src/sveltekit/sdk-example.js +1 -1
  124. package/dist/src/sveltekit/sdk-example.js.map +1 -1
  125. package/dist/src/sveltekit/sveltekit-wizard.js +3 -5
  126. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  127. package/dist/src/sveltekit/templates.js +28 -1
  128. package/dist/src/sveltekit/templates.js.map +1 -1
  129. package/dist/src/utils/clack/index.d.ts +13 -15
  130. package/dist/src/utils/clack/index.js +17 -50
  131. package/dist/src/utils/clack/index.js.map +1 -1
  132. package/dist/src/utils/git.d.ts +11 -0
  133. package/dist/src/utils/git.js +69 -0
  134. package/dist/src/utils/git.js.map +1 -0
  135. package/dist/src/utils/sentrycli-utils.js +13 -4
  136. package/dist/src/utils/sentrycli-utils.js.map +1 -1
  137. package/dist/src/version.d.ts +1 -1
  138. package/dist/src/version.js +1 -1
  139. package/dist/src/version.js.map +1 -1
  140. package/dist/test/angular/angular-wizard.test.d.ts +1 -0
  141. package/dist/test/angular/angular-wizard.test.js +27 -0
  142. package/dist/test/angular/angular-wizard.test.js.map +1 -0
  143. package/dist/test/angular/codemods/sourcemaps.test.d.ts +1 -0
  144. package/dist/test/angular/codemods/sourcemaps.test.js +237 -0
  145. package/dist/test/angular/codemods/sourcemaps.test.js.map +1 -0
  146. package/dist/test/angular/example-component.test.d.ts +1 -0
  147. package/dist/test/angular/example-component.test.js +105 -0
  148. package/dist/test/angular/example-component.test.js.map +1 -0
  149. package/dist/test/apple/code-tools.test.js +54 -35
  150. package/dist/test/apple/code-tools.test.js.map +1 -1
  151. package/dist/test/apple/configure-sentry-cli.test.d.ts +1 -0
  152. package/dist/test/apple/configure-sentry-cli.test.js +131 -0
  153. package/dist/test/apple/configure-sentry-cli.test.js.map +1 -0
  154. package/dist/test/apple/macos-system-helper-mocked.test.d.ts +1 -0
  155. package/dist/test/apple/macos-system-helper-mocked.test.js +46 -0
  156. package/dist/test/apple/macos-system-helper-mocked.test.js.map +1 -0
  157. package/dist/test/apple/macos-system-helper.test.d.ts +1 -0
  158. package/dist/test/apple/macos-system-helper.test.js +88 -0
  159. package/dist/test/apple/macos-system-helper.test.js.map +1 -0
  160. package/dist/test/apple/templates.test.js +10 -0
  161. package/dist/test/apple/templates.test.js.map +1 -1
  162. package/dist/test/apple/xcode-manager.test.js +745 -379
  163. package/dist/test/apple/xcode-manager.test.js.map +1 -1
  164. package/dist/test/flutter/templates.test.js +9 -0
  165. package/dist/test/flutter/templates.test.js.map +1 -1
  166. package/dist/test/react-native/javascript.test.js +119 -0
  167. package/dist/test/react-native/javascript.test.js.map +1 -1
  168. package/dist/test/react-native/metro.test.js +113 -0
  169. package/dist/test/react-native/metro.test.js.map +1 -1
  170. package/dist/test/remix/client-entry.test.js +10 -10
  171. package/dist/test/remix/client-entry.test.js.map +1 -1
  172. package/dist/test/utils/git.test.d.ts +1 -0
  173. package/dist/test/utils/git.test.js +70 -0
  174. package/dist/test/utils/git.test.js.map +1 -0
  175. package/package.json +1 -1
@@ -32,8 +32,10 @@ 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 debug_1 = require("../utils/debug");
35
36
  const templates = __importStar(require("./templates"));
36
37
  const xcode_1 = require("xcode");
38
+ const macos_system_helper_1 = require("./macos-system-helper");
37
39
  function setDebugInformationFormatAndSandbox(proj, targetName) {
38
40
  const xcObjects = proj.hash.project.objects;
39
41
  if (!xcObjects.PBXNativeTarget) {
@@ -196,6 +198,7 @@ function addUploadSymbolsScript(xcodeProject, sentryProject, targetName, uploadS
196
198
  }
197
199
  xcObjects.PBXShellScriptBuildPhase[key] = value;
198
200
  }
201
+ // Add the build phase to the target
199
202
  const isHomebrewInstalled = fs.existsSync('/opt/homebrew/bin/sentry-cli');
200
203
  xcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', targetKey, {
201
204
  inputFileListPaths: [],
@@ -207,17 +210,32 @@ function addUploadSymbolsScript(xcodeProject, sentryProject, targetName, uploadS
207
210
  clack.log.step(`Added Sentry upload script to "${targetName}" build phase`);
208
211
  }
209
212
  class XcodeProject {
210
- projectPath;
213
+ /**
214
+ * The directory where the Xcode project is located.
215
+ */
216
+ baseDir;
217
+ /**
218
+ * The path to the `<PROJECT>.xcodeproj` directory.
219
+ */
220
+ xcodeprojPath;
221
+ /**
222
+ * The path to the `project.pbxproj` file.
223
+ */
224
+ pbxprojPath;
225
+ /**
226
+ * The Xcode project object.
227
+ */
211
228
  project;
212
229
  objects;
213
- files;
214
230
  /**
215
231
  * Creates a new XcodeProject instance, a wrapper around the Xcode project file `<PROJECT>.xcodeproj/project.pbxproj`.
216
232
  *
217
233
  * @param projectPath - The path to the Xcode project file
218
234
  */
219
235
  constructor(projectPath) {
220
- this.projectPath = projectPath;
236
+ this.pbxprojPath = projectPath;
237
+ this.xcodeprojPath = path.dirname(projectPath);
238
+ this.baseDir = path.dirname(this.xcodeprojPath);
221
239
  this.project = (0, xcode_1.project)(projectPath);
222
240
  this.project.parseSync();
223
241
  this.objects = this.project.hash.project.objects;
@@ -244,85 +262,738 @@ class XcodeProject {
244
262
  addSentrySPM(this.project, target);
245
263
  }
246
264
  const newContent = this.project.writeSync();
247
- fs.writeFileSync(this.projectPath, newContent);
265
+ fs.writeFileSync(this.pbxprojPath, newContent);
248
266
  }
249
- filesForTarget(target) {
250
- const files = this.projectFiles();
251
- const fileDictionary = {};
252
- files.forEach((file) => {
253
- fileDictionary[file.key] = file.path;
254
- });
255
- const targets = this.objects.PBXNativeTarget || {};
256
- const nativeTarget = Object.keys(targets).filter((key) => {
257
- const value = targets[key];
258
- return (!key.endsWith('_comment') &&
259
- typeof value !== 'string' &&
260
- value.name === target);
261
- })[0];
262
- if (nativeTarget === undefined) {
267
+ /**
268
+ * Retrieves all source files associated with a specific target in the Xcode project.
269
+ * This is used to find files where we can inject Sentry initialization code.
270
+ *
271
+ * @param targetName - The name of the target to get files for
272
+ * @returns An array of absolute file paths for the target's source files, or undefined if target not found
273
+ */
274
+ getSourceFilesForTarget(targetName) {
275
+ // ## Summary how Xcode Projects are structured:
276
+ // - Every Xcode Project has exactly one main group of type `PBXGroup`
277
+ // - The main group contains a list of children identifiers
278
+ // - Each child can be a `PBXGroup`, a `PBXFileReference` or a `PBXFileSystemSynchronizedRootGroup`
279
+ // - Each `PBXGroup` has a list of children identifiers which again can be `PBXGroup`, `PBXFileReference` or `PBXFileSystemSynchronizedRootGroup`
280
+ // - The target defines the list of `fileSystemSynchronizedGroups` which are `PBXFileSystemSynchronizedRootGroup` to be included in the build phase
281
+ // - The `PBXFileSystemSynchronizedRootGroup` has a list of `exceptions` which are `PBXFileSystemSynchronizedBuildFileExceptionSet`
282
+ // - Each `PBXFileSystemSynchronizedBuildFileExceptionSet` represents a folder to be excluded from the build.
283
+ // - The `PBXFileSystemSynchronizedBuildFileExceptionSet` has a list of `membershipExceptions` which are files to be excluded from being excluded, therefore included in the build.
284
+ // - The Xcode project has a build phase `PBXSourcesBuildPhase` which has a list of `files` which are `PBXBuildFile`
285
+ // - A file which is not part of a `PBXFileSystemSynchronizedRootGroup` must be added to the `files` list of the `PBXSourcesBuildPhase` build phase
286
+ // - Nested subfolders in `fileSystemSynchronizedGroups` are not declared but recursively included
287
+ //
288
+ // Based on the findings above the files included in the build phase are:
289
+ // - All files in the `files` of the `PBXSourcesBuildPhase` build phase `Sources` of the target
290
+ // - All files in directories of the `fileSystemSynchronizedGroups` of the target
291
+ // - Excluding all files in the `exceptions` of the `PBXFileSystemSynchronizedRootGroup` of the target
292
+ // - Including all files in the `membershipExceptions` of the `PBXFileSystemSynchronizedBuildFileExceptionSet` of the target
293
+ const nativeTarget = this.findNativeTargetByName(targetName);
294
+ if (!nativeTarget) {
295
+ (0, debug_1.debug)('Target not found: ' + targetName);
263
296
  return undefined;
264
297
  }
265
- const buildPhaseKey = targets[nativeTarget].buildPhases?.filter((phase) => {
266
- return this.objects.PBXSourcesBuildPhase?.[phase.value] !== undefined;
267
- })[0];
268
- if (buildPhaseKey === undefined) {
298
+ const filesInBuildPhase = this.findFilesInSourceBuildPhase(nativeTarget);
299
+ (0, debug_1.debug)(`Found ${filesInBuildPhase.length} files in build phase for target: ${targetName}`);
300
+ const filesInSynchronizedRootGroups = this.findFilesInSynchronizedRootGroups(nativeTarget);
301
+ (0, debug_1.debug)(`Found ${filesInSynchronizedRootGroups.length} files in synchronized root groups for target: ${targetName}`);
302
+ return [...filesInBuildPhase, ...filesInSynchronizedRootGroups];
303
+ }
304
+ // ================================ TARGET HELPERS ================================
305
+ /**
306
+ * Finds a native target by name.
307
+ *
308
+ * @param targetName - The name of the target to find
309
+ * @returns The native target, or undefined if the target is not found
310
+ */
311
+ findNativeTargetByName(targetName) {
312
+ (0, debug_1.debug)('Finding native target by name: ' + targetName);
313
+ if (!this.objects.PBXNativeTarget) {
314
+ (0, debug_1.debug)('No native targets found');
269
315
  return undefined;
270
316
  }
271
- const buildPhase = this.objects.PBXSourcesBuildPhase?.[buildPhaseKey.value];
272
- const buildPhaseFiles = buildPhase?.files ?? [];
273
- const baseDir = path.dirname(path.dirname(this.projectPath));
274
- return buildPhaseFiles
275
- .map((file) => {
276
- const fileRef = this.objects.PBXBuildFile?.[file.value]?.fileRef;
277
- if (!fileRef) {
278
- return '';
317
+ const nativeTargets = Object.entries(this.objects.PBXNativeTarget);
318
+ for (const [key, target] of nativeTargets) {
319
+ // Ignore comments
320
+ if (key.endsWith('_comment') || typeof target === 'string') {
321
+ continue;
279
322
  }
280
- const buildFile = fileDictionary[fileRef];
281
- if (!buildFile) {
282
- return '';
323
+ // Ignore targets that are not the target we are looking for
324
+ if (target.name !== targetName) {
325
+ continue;
283
326
  }
284
- return path.join(baseDir, buildFile);
285
- })
286
- .filter((f) => f.length > 0);
287
- }
288
- projectFiles() {
289
- if (this.files === undefined) {
290
- const proj = this.project.getFirstProject();
291
- const mainGroupKey = proj.firstProject.mainGroup;
292
- const mainGroup = this.objects.PBXGroup?.[mainGroupKey];
293
- if (!mainGroup || typeof mainGroup === 'string') {
294
- return [];
327
+ (0, debug_1.debug)('Found native target: ' + targetName);
328
+ return {
329
+ id: key,
330
+ obj: target,
331
+ };
332
+ }
333
+ (0, debug_1.debug)('Target not found: ' + targetName);
334
+ return undefined;
335
+ }
336
+ // ================================ BUILD PHASE HELPERS ================================
337
+ /**
338
+ * Finds the source build phase in a target.
339
+ *
340
+ * @param target - The target to find the source build phase in
341
+ * @returns The source build phase, or undefined if the target is not found or has no source build phase
342
+ */
343
+ findSourceBuildPhaseInTarget(target) {
344
+ (0, debug_1.debug)(`Finding source build phase in target: ${target.name}`);
345
+ if (!target.buildPhases) {
346
+ (0, debug_1.debug)('No build phases found for target: ' + target.name);
347
+ return undefined;
348
+ }
349
+ for (const phase of target.buildPhases) {
350
+ const buildPhaseId = phase.value;
351
+ const buildPhase = this.objects.PBXSourcesBuildPhase?.[buildPhaseId];
352
+ if (typeof buildPhase !== 'object') {
353
+ // Ignore comments
354
+ continue;
295
355
  }
296
- this.files = this.buildGroup(mainGroup);
356
+ (0, debug_1.debug)(`Found source build phase: ${buildPhaseId} for target: ${target.name}`);
357
+ return {
358
+ id: buildPhaseId,
359
+ obj: buildPhase,
360
+ };
297
361
  }
298
- return this.files;
362
+ (0, debug_1.debug)(`No source build phase found for target: ${target.name}`);
363
+ return undefined;
299
364
  }
300
- buildGroup(group, path = '') {
365
+ // ================================ FILE HELPERS ================================
366
+ /**
367
+ * Finds all files in the source build phase of a target.
368
+ *
369
+ * @param nativeTarget - The target to find the files in
370
+ * @returns The files in the source build phase of the target, or an empty array if the target is not found or has no source build phase
371
+ */
372
+ findFilesInSourceBuildPhase(nativeTarget) {
373
+ (0, debug_1.debug)('Finding files in source build phase for target: ' +
374
+ nativeTarget.obj.name);
375
+ const buildPhase = this.findSourceBuildPhaseInTarget(nativeTarget.obj);
376
+ if (!buildPhase) {
377
+ (0, debug_1.debug)(`Sources build phase not found for target: ${nativeTarget.obj.name}`);
378
+ return [];
379
+ }
380
+ const buildPhaseFiles = buildPhase.obj.files;
381
+ if (!buildPhaseFiles) {
382
+ (0, debug_1.debug)(`No files found in sources build phase for target: ${nativeTarget.obj.name}`);
383
+ return [];
384
+ }
385
+ if (!this.objects.PBXBuildFile) {
386
+ (0, debug_1.debug)('PBXBuildFile is undefined');
387
+ return [];
388
+ }
389
+ if (!this.objects.PBXFileReference) {
390
+ (0, debug_1.debug)('PBXFileReference is undefined');
391
+ return [];
392
+ }
301
393
  const result = [];
302
- for (const child of group.children ?? []) {
303
- const fileReference = this.objects.PBXFileReference?.[child.value];
304
- const groupReference = this.objects.PBXGroup?.[child.value];
305
- if (fileReference) {
306
- if (typeof fileReference === 'string') {
307
- continue;
308
- }
394
+ for (const file of buildPhaseFiles) {
395
+ (0, debug_1.debug)(`Resolving build phase file: ${file.value}`);
396
+ // Find the related build file object
397
+ const buildFileObj = this.objects.PBXBuildFile[file.value];
398
+ if (!buildFileObj || typeof buildFileObj !== 'object') {
399
+ (0, debug_1.debug)(`Build file object not found for file: ${file.value}`);
400
+ continue;
401
+ }
402
+ (0, debug_1.debug)(`Build file object found for file: ${file.value}`);
403
+ const buildFileRefId = buildFileObj.fileRef;
404
+ if (!buildFileRefId) {
405
+ (0, debug_1.debug)(`File reference not found for file: ${file.value}`);
406
+ continue;
407
+ }
408
+ (0, debug_1.debug)(`Build file reference found for file: ${file.value}`);
409
+ // Find the related file reference object
410
+ const buildFile = this.objects.PBXFileReference[buildFileRefId];
411
+ if (!buildFile || typeof buildFile !== 'object') {
412
+ (0, debug_1.debug)(`File not found in file dictionary for file: ${file.value}`);
413
+ continue;
414
+ }
415
+ (0, debug_1.debug)(`Build file found in file dictionary for file: ${file.value}`);
416
+ // Resolve the path of the file based on the `sourceTree` property
417
+ const resolvedFilePath = this.resolveAbsolutePathOfFileReference({
418
+ id: buildFileRefId,
419
+ obj: buildFile,
420
+ });
421
+ if (!resolvedFilePath) {
422
+ (0, debug_1.debug)(`Failed to resolve file path for file: ${file.value}`);
423
+ continue;
424
+ }
425
+ (0, debug_1.debug)(`Resolved file ${file.value} to path: ${resolvedFilePath}`);
426
+ result.push(resolvedFilePath);
427
+ }
428
+ (0, debug_1.debug)(`Resolved ${result.length} files for target: ${nativeTarget.obj.name}`);
429
+ return result;
430
+ }
431
+ /**
432
+ * Resolves the absolute path of a file reference.
433
+ *
434
+ * @param fileRef - The file reference to resolve the path of
435
+ * @returns The absolute path of the file reference, or undefined if the file reference is not found or has no path
436
+ */
437
+ resolveAbsolutePathOfFileReference(fileRef) {
438
+ (0, debug_1.debug)(`Resolving path of file reference: ${fileRef.id} with path: ${fileRef.obj.path}`);
439
+ // File path is expected to be set, therefore typing is non-nullable.
440
+ // As the file is loaded from a project file, it is not guaranteed to be set,
441
+ // therefore we treat it as optional.
442
+ if (!fileRef.obj.path) {
443
+ (0, debug_1.debug)(`File reference path not found for file reference: ${fileRef.id}`);
444
+ return undefined;
445
+ }
446
+ // File references are resolved based on the `sourceTree` property
447
+ // which can have one of the following values:
448
+ // - '<absolute>': The file path is absolute
449
+ // - '<group>': The file path is relative to the parent group of the file reference
450
+ // - 'BUILT_PRODUCTS_DIR': The file path is relative to the built products directory, i.e. the build output directory in derived data
451
+ // - 'SOURCE_ROOT': The file path is relative to the source root, i.e. the directory where the Xcode project is located
452
+ // - 'SDKROOT': The file path is relative to the SDK root, i.e. the directory where the SDK is installed
453
+ // - 'DEVELOPER_DIR': The file path is relative to the developer directory, i.e. the directory where the Xcode command line tools are installed
454
+ // The default is '<group>'
455
+ const fileRefSourceTree = fileRef.obj.sourceTree?.replace(/"/g, '') ?? '';
456
+ switch (fileRefSourceTree) {
457
+ case '<absolute>':
458
+ return fileRef.obj.path.replace(/"/g, '');
459
+ case '<group>':
460
+ return this.resolveAbsoluteFilePathRelativeToGroup(fileRef);
461
+ case 'BUILT_PRODUCTS_DIR':
462
+ return this.resolveAbsoluteFilePathRelativeToBuiltProductsDir(fileRef);
463
+ case 'SOURCE_ROOT':
464
+ return this.resolveAbsoluteFilePathRelativeToSourceRoot(fileRef);
465
+ case 'SDKROOT':
466
+ return this.resolveAbsoluteFilePathRelativeToSdkRoot(fileRef);
467
+ case 'DEVELOPER_DIR':
468
+ return this.resolveAbsoluteFilePathRelativeToDeveloperDir(fileRef);
469
+ default:
470
+ (0, debug_1.debug)(`Unknown source tree '${fileRef.obj.sourceTree}' for build file: ${fileRef.obj.path}`);
471
+ return undefined;
472
+ }
473
+ }
474
+ /**
475
+ * Resolves the absolute path of a file reference relative to the parent group.
476
+ *
477
+ * @param fileRef - The file reference to resolve the path of
478
+ * @returns The absolute path of the file reference, or undefined if the file reference is not found or has no path
479
+ */
480
+ resolveAbsoluteFilePathRelativeToGroup(fileRef) {
481
+ (0, debug_1.debug)(`Resolving absolute file path relative to group for file reference: ${fileRef.id} with path: ${fileRef.obj.path ?? ''}`);
482
+ const fileRefPath = fileRef.obj.path?.replace(/"/g, '');
483
+ if (!fileRefPath) {
484
+ (0, debug_1.debug)(`File reference path not found for file reference: ${fileRef.id}`);
485
+ return undefined;
486
+ }
487
+ // Find the parent group of the file reference by searching for the reverse relationship
488
+ const parentGroup = this.findParentGroupByChildId(fileRef.id);
489
+ if (!parentGroup) {
490
+ (0, debug_1.debug)(`Parent group not found for file reference: ${fileRef.id} at path: ${fileRefPath}`);
491
+ return undefined;
492
+ }
493
+ // Resolve the path of the parent group
494
+ const absoluteGroupPath = this.resolveAbsolutePathOfGroup(parentGroup);
495
+ if (!absoluteGroupPath) {
496
+ (0, debug_1.debug)(`Failed to resolve path of group: ${parentGroup.id}`);
497
+ return undefined;
498
+ }
499
+ return path.join(absoluteGroupPath, fileRefPath);
500
+ }
501
+ /**
502
+ * Resolves the absolute path of a file reference relative to the built products directory.
503
+ *
504
+ * @param buildFile - The file reference to resolve the path of
505
+ * @returns The absolute path of the file reference, or undefined if the file reference is not found or has no path
506
+ */
507
+ resolveAbsoluteFilePathRelativeToBuiltProductsDir(buildFile) {
508
+ (0, debug_1.debug)(`Resolving absolute file path relative to built products directory for file reference: ${buildFile.id} with path: ${buildFile.obj.path ?? ''}`);
509
+ const builtProductsDir = this.getBuildProductsDirectoryPath();
510
+ if (!builtProductsDir) {
511
+ (0, debug_1.debug)(`Failed to resolve built products directory path`);
512
+ return undefined;
513
+ }
514
+ return path.join(builtProductsDir, buildFile.obj.path.replace(/"/g, ''));
515
+ }
516
+ /**
517
+ * Resolves the absolute path of a file reference relative to the source root.
518
+ *
519
+ * The source root is the directory where the `.xcodeproj` file is located.
520
+ *
521
+ * @param buildFile - The file reference to resolve the path of
522
+ * @returns The absolute path of the file reference, or undefined if the file reference is not found or has no path
523
+ */
524
+ resolveAbsoluteFilePathRelativeToSourceRoot(buildFile) {
525
+ return path.join(this.baseDir, buildFile.obj.path.replace(/"/g, ''));
526
+ }
527
+ /**
528
+ * Resolves the absolute path of a file reference relative to the SDK root.
529
+ *
530
+ * @param buildFile - The file reference to resolve the path of
531
+ * @returns The absolute path of the file reference, or undefined if the file reference is not found or has no path
532
+ */
533
+ resolveAbsoluteFilePathRelativeToSdkRoot(buildFile) {
534
+ (0, debug_1.debug)(`Resolving absolute file path relative to SDK root for file reference: ${buildFile.id} with path: ${buildFile.obj.path ?? ''}`);
535
+ const sdkRoot = macos_system_helper_1.MacOSSystemHelpers.findSDKRootDirectoryPath();
536
+ if (!sdkRoot) {
537
+ (0, debug_1.debug)(`Failed to resolve SDK root directory path`);
538
+ return undefined;
539
+ }
540
+ return path.join(sdkRoot, buildFile.obj.path.replace(/"/g, ''));
541
+ }
542
+ /**
543
+ * Resolves the absolute path of a file reference relative to the developer directory.
544
+ *
545
+ * @param buildFile - The file reference to resolve the path of
546
+ * @returns The absolute path of the file reference, or undefined if the file reference is not found or has no path
547
+ */
548
+ resolveAbsoluteFilePathRelativeToDeveloperDir(buildFile) {
549
+ (0, debug_1.debug)(`Resolving absolute file path relative to developer directory for file reference: ${buildFile.id} with path: ${buildFile.obj.path ?? ''}`);
550
+ const developerDir = macos_system_helper_1.MacOSSystemHelpers.findDeveloperDirectoryPath();
551
+ if (!developerDir) {
552
+ (0, debug_1.debug)(`Failed to resolve developer directory path`);
553
+ return undefined;
554
+ }
555
+ return path.join(developerDir, buildFile.obj.path.replace(/"/g, ''));
556
+ }
557
+ /**
558
+ * Resolves the absolute path of a group.
559
+ *
560
+ * @param group - The group to resolve the path of
561
+ * @returns The absolute path of the group, or undefined if the group is not found or has no path
562
+ */
563
+ resolveAbsolutePathOfGroup(group) {
564
+ (0, debug_1.debug)(`Resolving path of group: ${group.id} with path: ${group.obj.path ?? ''}`);
565
+ // Group paths are resolved based on the `sourceTree` property
566
+ // which can have one of the following values:
567
+ // - '<group>': The group path is relative to the parent group of the group
568
+ // - 'SOURCE_ROOT': The group path is relative to the source root, i.e. the directory where the Xcode project is located
569
+ // - 'BUILT_PRODUCTS_DIR': The group path is relative to the built products directory, i.e. the build output directory in derived data
570
+ // - 'SDKROOT': The group path is relative to the SDK root, i.e. the directory where the SDK is installed
571
+ // - 'DEVELOPER_DIR': The group path is relative to the developer directory, i.e. the directory where the Xcode command line tools are installed
572
+ // The default is '<group>'
573
+ const groupSourceTree = group.obj.sourceTree?.replace(/"/g, '') ?? '<group>';
574
+ switch (groupSourceTree) {
575
+ case '<group>':
576
+ return this.resolvePathOfGroupRelativeToGroup(group);
577
+ case 'SOURCE_ROOT':
578
+ return this.resolvePathOfGroupRelativeToSourceRoot(group);
579
+ case 'BUILT_PRODUCTS_DIR':
580
+ return this.resolvePathOfGroupRelativeToBuiltProductsDir(group);
581
+ case 'SDKROOT':
582
+ return this.resolvePathOfGroupRelativeToSdkRoot(group);
583
+ case 'DEVELOPER_DIR':
584
+ return this.resolvePathOfGroupRelativeToDeveloperDir(group);
585
+ default:
586
+ (0, debug_1.debug)(`Unknown source tree '${groupSourceTree}' for group: ${group.id}`);
587
+ return undefined;
588
+ }
589
+ }
590
+ /**
591
+ * Resolves the path of a group relative to the parent group.
592
+ *
593
+ * @param group - The group to resolve the path of
594
+ * @returns The path of the group relative to the parent group, or undefined if the group is not found or has no path
595
+ */
596
+ resolvePathOfGroupRelativeToGroup(group) {
597
+ const parentGroup = this.findParentGroupByChildId(group.id);
598
+ if (!parentGroup) {
599
+ (0, debug_1.debug)(`Parent group not found for group: ${group.id}`);
600
+ // If the parent group is not found, check if the group is the main group
601
+ // We assume the main group is at the root of the project
602
+ if (this.isMainGroup(group.id)) {
603
+ return this.baseDir;
604
+ }
605
+ return undefined;
606
+ }
607
+ const parentGroupPath = this.resolveAbsolutePathOfGroup(parentGroup);
608
+ if (!parentGroupPath) {
609
+ (0, debug_1.debug)(`Failed to resolve path of parent group: ${parentGroup.id}`);
610
+ return undefined;
611
+ }
612
+ const groupPath = group.obj.path?.replace(/"/g, '') ?? '';
613
+ if (!groupPath) {
614
+ (0, debug_1.debug)(`Group path not found for group: ${group.id}`);
615
+ return undefined;
616
+ }
617
+ return path.join(parentGroupPath, groupPath);
618
+ }
619
+ /**
620
+ * Resolves the path of a group relative to the source root.
621
+ *
622
+ * The source root is the directory where the `.xcodeproj` file is located.
623
+ *
624
+ * @param group - The group to resolve the path of
625
+ * @returns The path of the group relative to the source root, or undefined if the group is not found or has no path
626
+ */
627
+ resolvePathOfGroupRelativeToSourceRoot(group) {
628
+ const groupPath = group.obj.path?.replace(/"/g, '') ?? '';
629
+ if (!groupPath) {
630
+ (0, debug_1.debug)(`Group path not found for group: ${group.id}`);
631
+ return this.baseDir;
632
+ }
633
+ return path.join(this.baseDir, groupPath);
634
+ }
635
+ /**
636
+ * Resolves the path of a group relative to the built products directory.
637
+ *
638
+ * @param group - The group to resolve the path of
639
+ * @returns The path of the group relative to the built products directory, or undefined if the group is not found or has no path
640
+ */
641
+ resolvePathOfGroupRelativeToBuiltProductsDir(group) {
642
+ (0, debug_1.debug)(`Resolving path of group: ${group.id} relative to built products directory`);
643
+ const builtProductsDir = this.getBuildProductsDirectoryPath();
644
+ if (!builtProductsDir) {
645
+ (0, debug_1.debug)(`Failed to resolve built products directory path`);
646
+ return undefined;
647
+ }
648
+ return path.join(builtProductsDir, group.obj.path?.replace(/"/g, '') ?? '');
649
+ }
650
+ /**
651
+ * Resolves the path of a group relative to the SDK root.
652
+ *
653
+ * The SDK root is the directory where the SDK is installed.
654
+ *
655
+ * @param group - The group to resolve the path of
656
+ * @returns The path of the group relative to the SDK root, or undefined if the group is not found or has no path
657
+ */
658
+ resolvePathOfGroupRelativeToSdkRoot(group) {
659
+ (0, debug_1.debug)(`Resolving path of group: ${group.id} relative to SDK root`);
660
+ const sdkRoot = macos_system_helper_1.MacOSSystemHelpers.findSDKRootDirectoryPath();
661
+ if (!sdkRoot) {
662
+ (0, debug_1.debug)(`Failed to resolve SDK root directory path`);
663
+ return undefined;
664
+ }
665
+ return path.join(sdkRoot, group.obj.path?.replace(/"/g, '') ?? '');
666
+ }
667
+ /**
668
+ * Resolves the path of a group relative to the developer directory.
669
+ *
670
+ * The developer directory is the directory where the Xcode command line tools are installed.
671
+ *
672
+ * @param group - The group to resolve the path of
673
+ * @returns The path of the group relative to the developer directory, or undefined if the group is not found or has no path
674
+ */
675
+ resolvePathOfGroupRelativeToDeveloperDir(group) {
676
+ (0, debug_1.debug)(`Resolving path of group: ${group.id} relative to developer directory`);
677
+ const developerDir = macos_system_helper_1.MacOSSystemHelpers.findDeveloperDirectoryPath();
678
+ if (!developerDir) {
679
+ (0, debug_1.debug)(`Failed to resolve developer directory path`);
680
+ return undefined;
681
+ }
682
+ return path.join(developerDir, group.obj.path?.replace(/"/g, '') ?? '');
683
+ }
684
+ /**
685
+ * Resolves the absolute path of a group.
686
+ *
687
+ * @param group - The group to resolve the path of
688
+ * @returns The absolute path of the group, or undefined if the group is not found or has no path
689
+ */
690
+ resolveAbsolutePathOfSynchronizedRootGroup(group) {
691
+ (0, debug_1.debug)(`Resolving path of synchronized root group: ${group.id} with path: ${group.obj.path ?? ''}`);
692
+ // Group paths are resolved based on the `sourceTree` property
693
+ // which can have one of the following values:
694
+ // - '<group>': The group path is relative to the parent group of the group
695
+ // - 'SOURCE_ROOT': The group path is relative to the source root, i.e. the directory where the Xcode project is located
696
+ // - 'BUILT_PRODUCTS_DIR': The group path is relative to the built products directory, i.e. the build output directory in derived data
697
+ // - 'SDKROOT': The group path is relative to the SDK root, i.e. the directory where the SDK is installed
698
+ // - 'DEVELOPER_DIR': The group path is relative to the developer directory, i.e. the directory where the Xcode command line tools are installed
699
+ // The default is '<group>'
700
+ const groupSourceTree = group.obj.sourceTree?.replace(/"/g, '') ?? '<group>';
701
+ switch (groupSourceTree) {
702
+ case '<group>':
703
+ return this.resolvePathOfSynchronizedRootGroupRelativeToGroup(group);
704
+ case 'SOURCE_ROOT':
705
+ return this.resolvePathOfSynchronizedRootGroupRelativeToSourceRoot(group);
706
+ case 'BUILT_PRODUCTS_DIR':
707
+ return this.resolvePathOfSynchronizedRootGroupRelativeToBuiltProductsDir(group);
708
+ case 'SDKROOT':
709
+ return this.resolvePathOfSynchronizedRootGroupRelativeToSdkRoot(group);
710
+ case 'DEVELOPER_DIR':
711
+ return this.resolvePathOfSynchronizedRootGroupRelativeToDeveloperDir(group);
712
+ default:
713
+ (0, debug_1.debug)(`Unknown source tree '${groupSourceTree}' for group: ${group.id}`);
714
+ return undefined;
715
+ }
716
+ }
717
+ /**
718
+ * Resolves the path of a group relative to the parent group.
719
+ *
720
+ * @param group - The group to resolve the path of
721
+ * @returns The path of the group relative to the parent group, or undefined if the group is not found or has no path
722
+ */
723
+ resolvePathOfSynchronizedRootGroupRelativeToGroup(group) {
724
+ const parentGroup = this.findParentGroupByChildId(group.id);
725
+ if (!parentGroup) {
726
+ (0, debug_1.debug)(`Parent group not found for group: ${group.id}`);
727
+ // If the parent group is not found, check if the group is the main group
728
+ // We assume the main group is at the root of the project
729
+ if (this.isMainGroup(group.id)) {
730
+ return this.baseDir;
731
+ }
732
+ return undefined;
733
+ }
734
+ const parentGroupPath = this.resolveAbsolutePathOfGroup(parentGroup);
735
+ if (!parentGroupPath) {
736
+ (0, debug_1.debug)(`Failed to resolve path of parent group: ${parentGroup.id}`);
737
+ return undefined;
738
+ }
739
+ const groupPath = group.obj.path?.replace(/"/g, '') ?? '';
740
+ if (!groupPath) {
741
+ (0, debug_1.debug)(`Group path not found for group: ${group.id}`);
742
+ return undefined;
743
+ }
744
+ return path.join(parentGroupPath, groupPath);
745
+ }
746
+ /**
747
+ * Resolves the path of a group relative to the source root.
748
+ *
749
+ * The source root is the directory where the `.xcodeproj` file is located.
750
+ *
751
+ * @param group - The group to resolve the path of
752
+ * @returns The path of the group relative to the source root, or undefined if the group is not found or has no path
753
+ */
754
+ resolvePathOfSynchronizedRootGroupRelativeToSourceRoot(group) {
755
+ const groupPath = group.obj.path?.replace(/"/g, '') ?? '';
756
+ if (!groupPath) {
757
+ (0, debug_1.debug)(`Group path not found for group: ${group.id}`);
758
+ return this.baseDir;
759
+ }
760
+ return path.join(this.baseDir, groupPath);
761
+ }
762
+ /**
763
+ * Resolves the path of a group relative to the built products directory.
764
+ *
765
+ * @param group - The group to resolve the path of
766
+ * @returns The path of the group relative to the built products directory, or undefined if the group is not found or has no path
767
+ */
768
+ resolvePathOfSynchronizedRootGroupRelativeToBuiltProductsDir(group) {
769
+ (0, debug_1.debug)(`Resolving path of synchronized root group: ${group.id} relative to built products directory`);
770
+ const builtProductsDir = this.getBuildProductsDirectoryPath();
771
+ if (!builtProductsDir) {
772
+ (0, debug_1.debug)(`Failed to resolve built products directory path`);
773
+ return undefined;
774
+ }
775
+ return path.join(builtProductsDir, group.obj.path?.replace(/"/g, '') ?? '');
776
+ }
777
+ /**
778
+ * Resolves the path of a group relative to the SDK root.
779
+ *
780
+ * The SDK root is the directory where the SDK is installed.
781
+ *
782
+ * @param group - The group to resolve the path of
783
+ * @returns The path of the group relative to the SDK root, or undefined if the group is not found or has no path
784
+ */
785
+ resolvePathOfSynchronizedRootGroupRelativeToSdkRoot(group) {
786
+ (0, debug_1.debug)(`Resolving path of group: ${group.id} relative to SDK root`);
787
+ const sdkRoot = macos_system_helper_1.MacOSSystemHelpers.findSDKRootDirectoryPath();
788
+ if (!sdkRoot) {
789
+ (0, debug_1.debug)(`Failed to resolve SDK root directory path`);
790
+ return undefined;
791
+ }
792
+ return path.join(sdkRoot, group.obj.path?.replace(/"/g, '') ?? '');
793
+ }
794
+ /**
795
+ * Resolves the path of a group relative to the developer directory.
796
+ *
797
+ * The developer directory is the directory where the Xcode command line tools are installed.
798
+ *
799
+ * @param group - The group to resolve the path of
800
+ * @returns The path of the group relative to the developer directory, or undefined if the group is not found or has no path
801
+ */
802
+ resolvePathOfSynchronizedRootGroupRelativeToDeveloperDir(group) {
803
+ (0, debug_1.debug)(`Resolving path of synchronized root group: ${group.id} relative to developer directory`);
804
+ const developerDir = macos_system_helper_1.MacOSSystemHelpers.findDeveloperDirectoryPath();
805
+ if (!developerDir) {
806
+ (0, debug_1.debug)(`Failed to resolve developer directory path`);
807
+ return undefined;
808
+ }
809
+ return path.join(developerDir, group.obj.path?.replace(/"/g, '') ?? '');
810
+ }
811
+ /**
812
+ * Finds all files in the synchronized root groups of a target.
813
+ *
814
+ * @param nativeTarget - The target to find the files in
815
+ * @returns The files in the synchronized root groups of the target, or an empty array if the target is not found or has no synchronized root groups
816
+ */
817
+ findFilesInSynchronizedRootGroups(nativeTarget) {
818
+ (0, debug_1.debug)(`Finding files in synchronized root groups for target: ${nativeTarget.obj.name}`);
819
+ const synchronizedRootGroups = nativeTarget.obj.fileSystemSynchronizedGroups ?? [];
820
+ const result = [];
821
+ for (const group of synchronizedRootGroups) {
822
+ const groupObj = this.objects.PBXFileSystemSynchronizedRootGroup?.[group.value];
823
+ if (!groupObj || typeof groupObj !== 'object') {
824
+ (0, debug_1.debug)(`Synchronized root group not found: ${group.value}`);
825
+ continue;
826
+ }
827
+ (0, debug_1.debug)(`Found synchronized root group: ${group.value}`);
828
+ const files = this.getFilesInSynchronizedRootGroup({
829
+ id: group.value,
830
+ obj: groupObj,
831
+ });
832
+ (0, debug_1.debug)(`Found ${files.length} files in synchronized root group: ${group.value}`);
833
+ result.push(...files.map((file) => file.path));
834
+ }
835
+ (0, debug_1.debug)(`Found ${result.length} files in synchronized root groups for target: ${nativeTarget.obj.name}`);
836
+ return result;
837
+ }
838
+ getFilesInSynchronizedRootGroup(group) {
839
+ // Group path is expected to be set, therefore typing is non-nullable.
840
+ // As the group is loaded from a project file, it is not guaranteed to be set,
841
+ // therefore we treat it as optional.
842
+ if (!group.obj.path) {
843
+ (0, debug_1.debug)(`Group path not found for group: ${group.id}`);
844
+ return [];
845
+ }
846
+ // Resolve the path of the synchronized root group
847
+ const absoluteGroupPath = this.resolveAbsolutePathOfSynchronizedRootGroup(group);
848
+ if (!absoluteGroupPath) {
849
+ (0, debug_1.debug)(`Failed to resolve path of synchronized root group: ${group.id}`);
850
+ return [];
851
+ }
852
+ // Build a list of all exception paths for the group
853
+ const exceptionSets = this.getExceptionSetsForGroup(group);
854
+ // Resolve a list of all files in the group
855
+ const files = this.getAbsoluteFilePathsInDirectoryTree(absoluteGroupPath);
856
+ // Filter out files that are excluded by the exception sets
857
+ const filteredFiles = this.filterFilesByExceptionSets(files, exceptionSets);
858
+ return filteredFiles;
859
+ }
860
+ /**
861
+ * Returns all files in a directory tree.
862
+ *
863
+ * @param dirPath - The path of the directory to get the files in
864
+ * @returns All files in the directory tree, or an empty array if the directory does not exist
865
+ */
866
+ getAbsoluteFilePathsInDirectoryTree(dirPath) {
867
+ // If the directory does not exist, return an empty array
868
+ // This can happen if the group is not found in the project
869
+ if (!fs.existsSync(dirPath)) {
870
+ return [];
871
+ }
872
+ const result = [];
873
+ const files = fs.readdirSync(dirPath);
874
+ for (const file of files) {
875
+ // Ignore hidden files and directories
876
+ if (file.startsWith('.')) {
877
+ continue;
878
+ }
879
+ const filePath = path.join(dirPath, file);
880
+ // If the file is a directory, recursively get the files in the directory
881
+ if (fs.statSync(filePath).isDirectory()) {
882
+ result.push(...this.getAbsoluteFilePathsInDirectoryTree(filePath));
883
+ continue;
884
+ }
885
+ // If the file is a file, add it to the result
886
+ if (fs.statSync(filePath).isFile()) {
309
887
  result.push({
310
- key: child.value,
311
- path: `${path}${fileReference.path.replace(/"/g, '')}`,
888
+ name: file,
889
+ path: filePath,
312
890
  });
313
- }
314
- else if (groupReference) {
315
- if (typeof groupReference === 'string') {
316
- continue;
317
- }
318
- const groupChildren = this.buildGroup(groupReference, groupReference.path
319
- ? `${path}${groupReference.path.replace(/"/g, '')}/`
320
- : path);
321
- result.push(...groupChildren);
891
+ continue;
322
892
  }
323
893
  }
324
894
  return result;
325
895
  }
896
+ filterFilesByExceptionSets(files, exceptionSets) {
897
+ // Iterate over all files and filter out files that are excluded by any exception sets
898
+ return files.filter((file) => {
899
+ return !exceptionSets.some((exceptionSet) => {
900
+ const membershipExceptions = exceptionSet.obj.membershipExceptions ?? [];
901
+ return membershipExceptions.some((path) => {
902
+ const unescapedPath = path.replace(/"/g, '');
903
+ return file.path.includes(unescapedPath);
904
+ });
905
+ });
906
+ });
907
+ }
908
+ // ================================ GROUP HELPERS ================================
909
+ /**
910
+ * Returns all groups that are PBXGroup.
911
+ *
912
+ * This is a helper method to avoid having to map and filter the groups manually.
913
+ *
914
+ * @returns All groups that are PBXGroup, excluding comments and non-object values.
915
+ */
916
+ get groups() {
917
+ // Map and filter the groups to only include the groups that are PBXGroup
918
+ return Object.entries(this.objects.PBXGroup ?? {}).reduce((acc, [key, group]) => {
919
+ if (typeof group !== 'object') {
920
+ return acc;
921
+ }
922
+ return acc.concat([
923
+ {
924
+ id: key,
925
+ obj: group,
926
+ },
927
+ ]);
928
+ }, new Array());
929
+ }
930
+ /**
931
+ * Finds the parent group of a child group or file reference.
932
+ *
933
+ * @param childId - The ID of the child group or file reference
934
+ * @returns The parent group of the child group or file reference, or undefined if the child group or file reference is not found or has no parent group
935
+ */
936
+ findParentGroupByChildId(childId) {
937
+ return this.groups.find((group) => {
938
+ return (group.obj.children ?? []).some((child) => {
939
+ return child.value === childId;
940
+ });
941
+ });
942
+ }
943
+ /**
944
+ * Checks if a group is the main group of any project.
945
+ *
946
+ * @param groupId - The ID of the group to check
947
+ * @returns True if the group is the main group, false otherwise
948
+ */
949
+ isMainGroup(groupId) {
950
+ return Object.values(this.objects.PBXProject ?? {}).some((project) => {
951
+ if (typeof project !== 'object') {
952
+ return false;
953
+ }
954
+ return project.mainGroup === groupId;
955
+ });
956
+ }
957
+ getExceptionSetsForGroup(group) {
958
+ const exceptions = group.obj.exceptions ?? [];
959
+ const exceptionSets = [];
960
+ for (const exception of exceptions) {
961
+ const exceptionSet = this.objects.PBXFileSystemSynchronizedBuildFileExceptionSet?.[exception.value];
962
+ if (typeof exceptionSet !== 'object') {
963
+ continue;
964
+ }
965
+ exceptionSets.push({
966
+ id: exception.value,
967
+ obj: exceptionSet,
968
+ comment: exception.comment,
969
+ });
970
+ }
971
+ return exceptionSets;
972
+ }
973
+ /**
974
+ * The path to the build products directory for the project.
975
+ *
976
+ * This is cached to avoid having to read the build settings from Xcode for each call to `getBuildProductsDirectoryPath`.
977
+ */
978
+ buildProductsDir;
979
+ /**
980
+ * Returns the path to the build products directory for the project.
981
+ *
982
+ * @returns The path to the build products directory for the project, or undefined if the path is not found
983
+ */
984
+ getBuildProductsDirectoryPath() {
985
+ if (this.buildProductsDir) {
986
+ return this.buildProductsDir;
987
+ }
988
+ const buildSettings = macos_system_helper_1.MacOSSystemHelpers.readXcodeBuildSettings(this.xcodeprojPath);
989
+ if (!buildSettings) {
990
+ (0, debug_1.debug)(`Failed to read Xcode build settings`);
991
+ return undefined;
992
+ }
993
+ this.buildProductsDir =
994
+ buildSettings['TARGET_BUILD_DIR'] ?? buildSettings['BUILD_DIR'];
995
+ return this.buildProductsDir;
996
+ }
326
997
  }
327
998
  exports.XcodeProject = XcodeProject;
328
999
  //# sourceMappingURL=xcode-manager.js.map