ios-app-review-plugin 1.0.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 (205) hide show
  1. package/.claude/settings.local.json +42 -0
  2. package/.github/actions/ios-review/action.yml +106 -0
  3. package/.github/workflows/ci.yml +103 -0
  4. package/.github/workflows/publish.yml +57 -0
  5. package/CHANGELOG.md +66 -0
  6. package/CONTRIBUTING.md +175 -0
  7. package/LICENSE +21 -0
  8. package/README.md +205 -0
  9. package/bitrise/step.sh +128 -0
  10. package/bitrise/step.yml +101 -0
  11. package/dist/analyzer.d.ts.map +1 -0
  12. package/dist/analyzers/asc-iap.d.ts.map +1 -0
  13. package/dist/analyzers/asc-metadata.d.ts.map +1 -0
  14. package/dist/analyzers/asc-screenshots.d.ts.map +1 -0
  15. package/dist/analyzers/asc-version.d.ts.map +1 -0
  16. package/dist/analyzers/code-scanner.d.ts.map +1 -0
  17. package/dist/analyzers/deprecated-api.d.ts.map +1 -0
  18. package/dist/analyzers/entitlements.d.ts.map +1 -0
  19. package/dist/analyzers/index.d.ts.map +1 -0
  20. package/dist/analyzers/info-plist.d.ts.map +1 -0
  21. package/dist/analyzers/privacy.d.ts.map +1 -0
  22. package/dist/analyzers/private-api.d.ts.map +1 -0
  23. package/dist/analyzers/security.d.ts.map +1 -0
  24. package/dist/analyzers/ui-ux.d.ts.map +1 -0
  25. package/dist/asc/auth.d.ts.map +1 -0
  26. package/dist/asc/client.d.ts.map +1 -0
  27. package/dist/asc/endpoints/apps.d.ts.map +1 -0
  28. package/dist/asc/endpoints/iap.d.ts.map +1 -0
  29. package/dist/asc/endpoints/screenshots.d.ts.map +1 -0
  30. package/dist/asc/endpoints/versions.d.ts.map +1 -0
  31. package/dist/asc/errors.d.ts.map +1 -0
  32. package/dist/asc/index.d.ts.map +1 -0
  33. package/dist/asc/types.d.ts.map +1 -0
  34. package/dist/badge/generator.d.ts.map +1 -0
  35. package/dist/badge/index.d.ts.map +1 -0
  36. package/dist/badge/types.d.ts.map +1 -0
  37. package/dist/cache/file-cache.d.ts.map +1 -0
  38. package/dist/cache/index.d.ts.map +1 -0
  39. package/dist/cache/types.d.ts.map +1 -0
  40. package/dist/cli/commands/help.d.ts.map +1 -0
  41. package/dist/cli/commands/scan.d.ts.map +1 -0
  42. package/dist/cli/commands/version.d.ts.map +1 -0
  43. package/dist/cli/index.d.ts.map +1 -0
  44. package/dist/cli/types.d.ts.map +1 -0
  45. package/dist/git/diff.d.ts.map +1 -0
  46. package/dist/git/index.d.ts.map +1 -0
  47. package/dist/git/types.d.ts.map +1 -0
  48. package/dist/guidelines/database.d.ts.map +1 -0
  49. package/dist/guidelines/index.d.ts.map +1 -0
  50. package/dist/guidelines/matcher.d.ts.map +1 -0
  51. package/dist/guidelines/types.d.ts.map +1 -0
  52. package/dist/history/comparator.d.ts.map +1 -0
  53. package/dist/history/index.d.ts.map +1 -0
  54. package/dist/history/store.d.ts.map +1 -0
  55. package/dist/history/types.d.ts.map +1 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +994 -0
  58. package/dist/parsers/index.d.ts.map +1 -0
  59. package/dist/parsers/plist.d.ts.map +1 -0
  60. package/dist/parsers/xcodeproj.d.ts.map +1 -0
  61. package/dist/progress/index.d.ts.map +1 -0
  62. package/dist/progress/reporter.d.ts.map +1 -0
  63. package/dist/progress/types.d.ts.map +1 -0
  64. package/dist/reports/html.d.ts.map +1 -0
  65. package/dist/reports/index.d.ts.map +1 -0
  66. package/dist/reports/json.d.ts.map +1 -0
  67. package/dist/reports/markdown.d.ts.map +1 -0
  68. package/dist/reports/types.d.ts.map +1 -0
  69. package/dist/rules/engine.d.ts.map +1 -0
  70. package/dist/rules/index.d.ts.map +1 -0
  71. package/dist/rules/loader.d.ts.map +1 -0
  72. package/dist/rules/types.d.ts.map +1 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/docs/ANALYZERS.md +237 -0
  75. package/docs/API.md +308 -0
  76. package/docs/BADGES.md +130 -0
  77. package/docs/CI_CD.md +283 -0
  78. package/docs/CLI.md +140 -0
  79. package/docs/REPORTS.md +212 -0
  80. package/docs/ROADMAP.md +267 -0
  81. package/docs/RULES.md +182 -0
  82. package/docs/SECURITY.md +89 -0
  83. package/docs/TROUBLESHOOTING.md +227 -0
  84. package/docs/tutorials/ASC_SETUP.md +188 -0
  85. package/docs/tutorials/CI_INTEGRATION.md +292 -0
  86. package/docs/tutorials/CUSTOM_RULES.md +291 -0
  87. package/docs/tutorials/GETTING_STARTED.md +226 -0
  88. package/docs/video-scripts/01-introduction.md +106 -0
  89. package/docs/video-scripts/02-cli-usage.md +120 -0
  90. package/docs/video-scripts/03-ci-integration.md +198 -0
  91. package/eslint.config.js +33 -0
  92. package/examples/.ios-review-rules.json +82 -0
  93. package/examples/bitrise-workflow.yml +129 -0
  94. package/examples/fastlane-lane.rb +71 -0
  95. package/examples/github-action.yml +147 -0
  96. package/fastlane/Fastfile.example +114 -0
  97. package/fastlane/README.md +99 -0
  98. package/jest.config.js +36 -0
  99. package/package.json +65 -0
  100. package/scripts/benchmark.ts +112 -0
  101. package/scripts/debug-parser.ts +37 -0
  102. package/scripts/debug-pbxproj.ts +36 -0
  103. package/scripts/debug-specific.ts +47 -0
  104. package/scripts/test-analyze.ts +67 -0
  105. package/scripts/xcode-cloud-review.sh +167 -0
  106. package/src/analyzer.ts +227 -0
  107. package/src/analyzers/asc-iap.ts +300 -0
  108. package/src/analyzers/asc-metadata.ts +326 -0
  109. package/src/analyzers/asc-screenshots.ts +310 -0
  110. package/src/analyzers/asc-version.ts +368 -0
  111. package/src/analyzers/code-scanner.ts +408 -0
  112. package/src/analyzers/deprecated-api.ts +390 -0
  113. package/src/analyzers/entitlements.ts +345 -0
  114. package/src/analyzers/index.ts +12 -0
  115. package/src/analyzers/info-plist.ts +409 -0
  116. package/src/analyzers/privacy.ts +376 -0
  117. package/src/analyzers/private-api.ts +377 -0
  118. package/src/analyzers/security.ts +327 -0
  119. package/src/analyzers/ui-ux.ts +509 -0
  120. package/src/asc/auth.ts +204 -0
  121. package/src/asc/client.ts +258 -0
  122. package/src/asc/endpoints/apps.ts +115 -0
  123. package/src/asc/endpoints/iap.ts +171 -0
  124. package/src/asc/endpoints/screenshots.ts +164 -0
  125. package/src/asc/endpoints/versions.ts +174 -0
  126. package/src/asc/errors.ts +109 -0
  127. package/src/asc/index.ts +108 -0
  128. package/src/asc/types.ts +369 -0
  129. package/src/badge/generator.ts +48 -0
  130. package/src/badge/index.ts +2 -0
  131. package/src/badge/types.ts +5 -0
  132. package/src/cache/file-cache.ts +75 -0
  133. package/src/cache/index.ts +2 -0
  134. package/src/cache/types.ts +10 -0
  135. package/src/cli/commands/help.ts +41 -0
  136. package/src/cli/commands/scan.ts +44 -0
  137. package/src/cli/commands/version.ts +12 -0
  138. package/src/cli/index.ts +92 -0
  139. package/src/cli/types.ts +17 -0
  140. package/src/git/diff.ts +21 -0
  141. package/src/git/index.ts +2 -0
  142. package/src/git/types.ts +5 -0
  143. package/src/guidelines/database.ts +344 -0
  144. package/src/guidelines/index.ts +4 -0
  145. package/src/guidelines/matcher.ts +84 -0
  146. package/src/guidelines/types.ts +28 -0
  147. package/src/history/comparator.ts +114 -0
  148. package/src/history/index.ts +3 -0
  149. package/src/history/store.ts +135 -0
  150. package/src/history/types.ts +40 -0
  151. package/src/index.ts +1113 -0
  152. package/src/parsers/index.ts +3 -0
  153. package/src/parsers/plist.ts +253 -0
  154. package/src/parsers/xcodeproj.ts +265 -0
  155. package/src/progress/index.ts +2 -0
  156. package/src/progress/reporter.ts +65 -0
  157. package/src/progress/types.ts +9 -0
  158. package/src/reports/html.ts +322 -0
  159. package/src/reports/index.ts +20 -0
  160. package/src/reports/json.ts +92 -0
  161. package/src/reports/markdown.ts +187 -0
  162. package/src/reports/types.ts +26 -0
  163. package/src/rules/engine.ts +121 -0
  164. package/src/rules/index.ts +3 -0
  165. package/src/rules/loader.ts +83 -0
  166. package/src/rules/types.ts +25 -0
  167. package/src/types/index.ts +247 -0
  168. package/tests/analyzer.test.ts +142 -0
  169. package/tests/analyzers/asc-iap.test.ts +228 -0
  170. package/tests/analyzers/asc-metadata.test.ts +210 -0
  171. package/tests/analyzers/asc-screenshots.test.ts +135 -0
  172. package/tests/analyzers/asc-version.test.ts +259 -0
  173. package/tests/analyzers/code-scanner.test.ts +745 -0
  174. package/tests/analyzers/deprecated-api.test.ts +286 -0
  175. package/tests/analyzers/entitlements.test.ts +411 -0
  176. package/tests/analyzers/info-plist.test.ts +148 -0
  177. package/tests/analyzers/privacy.test.ts +623 -0
  178. package/tests/analyzers/private-api.test.ts +255 -0
  179. package/tests/analyzers/security.test.ts +300 -0
  180. package/tests/analyzers/ui-ux.test.ts +357 -0
  181. package/tests/asc/auth.test.ts +189 -0
  182. package/tests/asc/client.test.ts +207 -0
  183. package/tests/asc/endpoints.test.ts +1359 -0
  184. package/tests/badge/generator.test.ts +73 -0
  185. package/tests/cache/file-cache.test.ts +124 -0
  186. package/tests/cli/cli-index.test.ts +510 -0
  187. package/tests/cli/commands.test.ts +67 -0
  188. package/tests/cli/scan.test.ts +152 -0
  189. package/tests/git/diff.test.ts +69 -0
  190. package/tests/guidelines/matcher.test.ts +209 -0
  191. package/tests/history/comparator.test.ts +272 -0
  192. package/tests/history/store.test.ts +200 -0
  193. package/tests/integration/cli.test.ts +95 -0
  194. package/tests/integration/e2e.test.ts +130 -0
  195. package/tests/parsers/plist.test.ts +240 -0
  196. package/tests/parsers/xcodeproj.test.ts +289 -0
  197. package/tests/progress/reporter.test.ts +117 -0
  198. package/tests/reports/html.test.ts +176 -0
  199. package/tests/reports/json.test.ts +235 -0
  200. package/tests/reports/markdown.test.ts +196 -0
  201. package/tests/rules/engine.test.ts +229 -0
  202. package/tests/rules/loader.test.ts +187 -0
  203. package/tests/setup.ts +15 -0
  204. package/tsconfig.json +27 -0
  205. package/tsconfig.test.json +9 -0
@@ -0,0 +1,368 @@
1
+ /**
2
+ * App Store Connect Version Analyzer
3
+ *
4
+ * Validates version information including local vs ASC version comparison,
5
+ * build numbers, version format, and submission status.
6
+ */
7
+
8
+ import type {
9
+ Analyzer,
10
+ AnalysisResult,
11
+ AnalyzerOptions,
12
+ Issue,
13
+ XcodeProject,
14
+ } from '../types/index.js';
15
+ import {
16
+ hasCredentials,
17
+ getAppByBundleId,
18
+ getLatestVersion,
19
+ getLatestBuild,
20
+ getEditableVersion,
21
+ getVersionWithLocalizations,
22
+ isASCError,
23
+ type AppStoreState,
24
+ } from '../asc/index.js';
25
+ import { parsePlist } from '../parsers/plist.js';
26
+ import type { InfoPlist } from '../types/index.js';
27
+
28
+ /**
29
+ * Version states that indicate issues
30
+ */
31
+ const REJECTED_STATES: AppStoreState[] = ['REJECTED', 'METADATA_REJECTED', 'INVALID_BINARY', 'DEVELOPER_REJECTED'];
32
+ const IN_REVIEW_STATES: AppStoreState[] = ['WAITING_FOR_REVIEW', 'IN_REVIEW'];
33
+ const PENDING_STATES: AppStoreState[] = ['PENDING_APPLE_RELEASE', 'PENDING_DEVELOPER_RELEASE', 'PENDING_CONTRACT'];
34
+
35
+ export class ASCVersionAnalyzer implements Analyzer {
36
+ name = 'ASC Version Analyzer';
37
+ description = 'Validates version and build information against App Store Connect';
38
+
39
+ async analyze(project: XcodeProject, options?: AnalyzerOptions): Promise<AnalysisResult> {
40
+ const startTime = Date.now();
41
+ const issues: Issue[] = [];
42
+
43
+ if (!hasCredentials()) {
44
+ issues.push({
45
+ id: 'asc-credentials-not-configured',
46
+ title: 'App Store Connect credentials not configured',
47
+ description:
48
+ 'ASC credentials are not configured. Set environment variables to enable version validation.',
49
+ severity: 'info',
50
+ category: 'version',
51
+ });
52
+
53
+ return {
54
+ analyzer: this.name,
55
+ passed: true,
56
+ issues,
57
+ duration: Date.now() - startTime,
58
+ };
59
+ }
60
+
61
+ const bundleId = options?.bundleId ?? this.getBundleIdFromProject(project);
62
+ if (!bundleId) {
63
+ issues.push({
64
+ id: 'asc-no-bundle-id',
65
+ title: 'No bundle ID found',
66
+ description: 'Could not determine bundle ID from project.',
67
+ severity: 'warning',
68
+ category: 'version',
69
+ });
70
+
71
+ return {
72
+ analyzer: this.name,
73
+ passed: true,
74
+ issues,
75
+ duration: Date.now() - startTime,
76
+ };
77
+ }
78
+
79
+ // Get local version info
80
+ const localVersion = await this.getLocalVersion(project);
81
+
82
+ try {
83
+ const versionIssues = await this.validateVersionsForBundleId(bundleId, localVersion);
84
+ issues.push(...versionIssues);
85
+ } catch (error) {
86
+ if (isASCError(error)) {
87
+ issues.push({
88
+ id: error.code,
89
+ title: error.name,
90
+ description: error.message,
91
+ severity: 'error',
92
+ category: 'version',
93
+ });
94
+ } else {
95
+ issues.push({
96
+ id: 'asc-api-error',
97
+ title: 'App Store Connect API Error',
98
+ description: error instanceof Error ? error.message : String(error),
99
+ severity: 'error',
100
+ category: 'version',
101
+ });
102
+ }
103
+ }
104
+
105
+ return {
106
+ analyzer: this.name,
107
+ passed: issues.filter((i) => i.severity === 'error').length === 0,
108
+ issues,
109
+ duration: Date.now() - startTime,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Compare versions for a bundle ID
115
+ */
116
+ async compareVersions(
117
+ bundleId: string,
118
+ localVersionString?: string,
119
+ localBuildNumber?: string
120
+ ): Promise<AnalysisResult> {
121
+ const startTime = Date.now();
122
+ const issues: Issue[] = [];
123
+
124
+ if (!hasCredentials()) {
125
+ return {
126
+ analyzer: this.name,
127
+ passed: false,
128
+ issues: [
129
+ {
130
+ id: 'asc-credentials-not-configured',
131
+ title: 'App Store Connect credentials not configured',
132
+ description: 'Set ASC environment variables to enable validation.',
133
+ severity: 'error',
134
+ category: 'version',
135
+ },
136
+ ],
137
+ duration: Date.now() - startTime,
138
+ };
139
+ }
140
+
141
+ try {
142
+ const versionIssues = await this.validateVersionsForBundleId(bundleId, {
143
+ version: localVersionString,
144
+ build: localBuildNumber,
145
+ });
146
+ issues.push(...versionIssues);
147
+ } catch (error) {
148
+ if (isASCError(error)) {
149
+ issues.push({
150
+ id: error.code,
151
+ title: error.name,
152
+ description: error.message,
153
+ severity: 'error',
154
+ category: 'version',
155
+ });
156
+ } else {
157
+ throw error;
158
+ }
159
+ }
160
+
161
+ return {
162
+ analyzer: this.name,
163
+ passed: issues.filter((i) => i.severity === 'error').length === 0,
164
+ issues,
165
+ duration: Date.now() - startTime,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Validate versions
171
+ */
172
+ private async validateVersionsForBundleId(
173
+ bundleId: string,
174
+ localVersion?: { version?: string | undefined; build?: string | undefined }
175
+ ): Promise<Issue[]> {
176
+ const issues: Issue[] = [];
177
+
178
+ const app = await getAppByBundleId(bundleId);
179
+
180
+ // Get latest version and build from ASC
181
+ const [latestVersion, latestBuild, editableVersion] = await Promise.all([
182
+ getLatestVersion(app.id),
183
+ getLatestBuild(app.id),
184
+ getEditableVersion(app.id),
185
+ ]);
186
+
187
+ // Check submission status of editable version
188
+ if (editableVersion) {
189
+ const state = editableVersion.attributes.appStoreState;
190
+
191
+ if (REJECTED_STATES.includes(state)) {
192
+ issues.push({
193
+ id: 'asc-version-rejected',
194
+ title: 'App version rejected',
195
+ description: `Version ${editableVersion.attributes.versionString} has been rejected (${state}). Review the rejection reason in App Store Connect.`,
196
+ severity: 'error',
197
+ category: 'version',
198
+ suggestion: 'Check App Store Connect for rejection details and required changes.',
199
+ });
200
+ } else if (IN_REVIEW_STATES.includes(state)) {
201
+ issues.push({
202
+ id: 'asc-version-in-review',
203
+ title: 'App version in review',
204
+ description: `Version ${editableVersion.attributes.versionString} is currently ${state === 'WAITING_FOR_REVIEW' ? 'waiting for review' : 'in review'}.`,
205
+ severity: 'info',
206
+ category: 'version',
207
+ });
208
+ } else if (PENDING_STATES.includes(state)) {
209
+ issues.push({
210
+ id: 'asc-version-pending',
211
+ title: 'App version pending release',
212
+ description: `Version ${editableVersion.attributes.versionString} is ${state.toLowerCase().replace(/_/g, ' ')}.`,
213
+ severity: 'info',
214
+ category: 'version',
215
+ });
216
+ }
217
+
218
+ // Check version localizations
219
+ const versionData = await getVersionWithLocalizations(editableVersion.id);
220
+
221
+ // Check "What's New" text
222
+ for (const loc of versionData.localizations) {
223
+ if (!loc.attributes.whatsNew) {
224
+ issues.push({
225
+ id: 'asc-missing-whats-new',
226
+ title: `Missing "What's New" text (${loc.attributes.locale})`,
227
+ description: 'Release notes are required for App Store updates.',
228
+ severity: 'warning',
229
+ category: 'version',
230
+ suggestion: 'Add release notes describing what changed in this version.',
231
+ });
232
+ }
233
+
234
+ // Check description
235
+ if (!loc.attributes.description) {
236
+ issues.push({
237
+ id: 'asc-missing-description',
238
+ title: `Missing description (${loc.attributes.locale})`,
239
+ description: 'App description is required.',
240
+ severity: 'error',
241
+ category: 'version',
242
+ });
243
+ }
244
+
245
+ // Check support URL
246
+ if (!loc.attributes.supportUrl) {
247
+ issues.push({
248
+ id: 'asc-missing-support-url',
249
+ title: `Missing support URL (${loc.attributes.locale})`,
250
+ description: 'Support URL is required for App Store submission.',
251
+ severity: 'error',
252
+ category: 'version',
253
+ });
254
+ }
255
+ }
256
+
257
+ // Check if build is attached
258
+ if (!versionData.build) {
259
+ issues.push({
260
+ id: 'asc-no-build-attached',
261
+ title: 'No build attached to version',
262
+ description: `Version ${editableVersion.attributes.versionString} does not have a build attached.`,
263
+ severity: 'warning',
264
+ category: 'version',
265
+ suggestion: 'Upload a build and attach it to this version in App Store Connect.',
266
+ });
267
+ }
268
+ }
269
+
270
+ // Compare local vs ASC versions
271
+ if (localVersion?.version && latestVersion) {
272
+ const ascVersion = latestVersion.attributes.versionString;
273
+ const comparison = this.compareVersionStrings(localVersion.version, ascVersion);
274
+
275
+ if (comparison < 0) {
276
+ issues.push({
277
+ id: 'asc-local-version-lower',
278
+ title: 'Local version is lower than ASC',
279
+ description: `Local version (${localVersion.version}) is lower than the latest ASC version (${ascVersion}).`,
280
+ severity: 'warning',
281
+ category: 'version',
282
+ suggestion: 'Update your local version number before submitting.',
283
+ });
284
+ } else if (comparison === 0 && editableVersion) {
285
+ issues.push({
286
+ id: 'asc-version-match',
287
+ title: 'Version already exists in ASC',
288
+ description: `Local version (${localVersion.version}) matches an existing version in App Store Connect.`,
289
+ severity: 'info',
290
+ category: 'version',
291
+ });
292
+ }
293
+ }
294
+
295
+ // Check build number
296
+ if (localVersion?.build && latestBuild) {
297
+ const ascBuild = latestBuild.attributes.version;
298
+ const localBuildNum = parseInt(localVersion.build, 10);
299
+ const ascBuildNum = parseInt(ascBuild, 10);
300
+
301
+ if (!isNaN(localBuildNum) && !isNaN(ascBuildNum) && localBuildNum <= ascBuildNum) {
302
+ issues.push({
303
+ id: 'asc-build-number-not-incremented',
304
+ title: 'Build number not incremented',
305
+ description: `Local build number (${localVersion.build}) must be greater than the latest ASC build (${ascBuild}).`,
306
+ severity: 'error',
307
+ category: 'version',
308
+ suggestion: `Increment your build number to at least ${ascBuildNum + 1}.`,
309
+ });
310
+ }
311
+ }
312
+
313
+ return issues;
314
+ }
315
+
316
+ /**
317
+ * Compare version strings (semver-like)
318
+ * Returns: -1 if a < b, 0 if a == b, 1 if a > b
319
+ */
320
+ private compareVersionStrings(a: string, b: string): number {
321
+ const partsA = a.split('.').map((n) => parseInt(n, 10) || 0);
322
+ const partsB = b.split('.').map((n) => parseInt(n, 10) || 0);
323
+
324
+ const maxLength = Math.max(partsA.length, partsB.length);
325
+
326
+ for (let i = 0; i < maxLength; i++) {
327
+ const numA = partsA[i] ?? 0;
328
+ const numB = partsB[i] ?? 0;
329
+
330
+ if (numA < numB) {
331
+ return -1;
332
+ }
333
+ if (numA > numB) {
334
+ return 1;
335
+ }
336
+ }
337
+
338
+ return 0;
339
+ }
340
+
341
+ /**
342
+ * Get local version from project Info.plist
343
+ */
344
+ private async getLocalVersion(
345
+ project: XcodeProject
346
+ ): Promise<{ version?: string | undefined; build?: string | undefined }> {
347
+ const appTarget = project.targets.find((t) => t.type === 'application');
348
+
349
+ if (!appTarget?.infoPlistPath) {
350
+ return {};
351
+ }
352
+
353
+ try {
354
+ const plist = await parsePlist<InfoPlist>(appTarget.infoPlistPath);
355
+ return {
356
+ version: plist.CFBundleShortVersionString,
357
+ build: plist.CFBundleVersion,
358
+ };
359
+ } catch {
360
+ return {};
361
+ }
362
+ }
363
+
364
+ private getBundleIdFromProject(project: XcodeProject): string | undefined {
365
+ const appTarget = project.targets.find((t) => t.type === 'application');
366
+ return appTarget?.bundleIdentifier;
367
+ }
368
+ }