@sentry/wizard 3.40.0 → 3.41.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 (103) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/README.md +19 -19
  3. package/bin.ts +5 -0
  4. package/codecov.yml +15 -0
  5. package/dist/bin.js +4 -0
  6. package/dist/bin.js.map +1 -1
  7. package/dist/e2e-tests/jest.config.d.ts +1 -0
  8. package/dist/e2e-tests/jest.config.js +1 -0
  9. package/dist/e2e-tests/jest.config.js.map +1 -1
  10. package/dist/package.json +3 -2
  11. package/dist/src/apple/apple-wizard.js +1 -2
  12. package/dist/src/apple/apple-wizard.js.map +1 -1
  13. package/dist/src/apple/code-tools.d.ts +10 -0
  14. package/dist/src/apple/code-tools.js +16 -12
  15. package/dist/src/apple/code-tools.js.map +1 -1
  16. package/dist/src/apple/fastlane.d.ts +23 -0
  17. package/dist/src/apple/fastlane.js +11 -7
  18. package/dist/src/apple/fastlane.js.map +1 -1
  19. package/dist/src/apple/templates.d.ts +1 -1
  20. package/dist/src/apple/templates.js +0 -2
  21. package/dist/src/apple/templates.js.map +1 -1
  22. package/dist/src/apple/xcode-manager.d.ts +10 -6
  23. package/dist/src/apple/xcode-manager.js +146 -61
  24. package/dist/src/apple/xcode-manager.js.map +1 -1
  25. package/dist/src/nextjs/nextjs-wizard.js +5 -3
  26. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  27. package/dist/src/nuxt/nuxt-wizard.js +6 -4
  28. package/dist/src/nuxt/nuxt-wizard.js.map +1 -1
  29. package/dist/src/nuxt/sdk-setup.d.ts +1 -1
  30. package/dist/src/nuxt/sdk-setup.js +2 -1
  31. package/dist/src/nuxt/sdk-setup.js.map +1 -1
  32. package/dist/src/react-native/react-native-wizard.js +5 -3
  33. package/dist/src/react-native/react-native-wizard.js.map +1 -1
  34. package/dist/src/remix/remix-wizard.js +5 -3
  35. package/dist/src/remix/remix-wizard.js.map +1 -1
  36. package/dist/src/run.d.ts +1 -0
  37. package/dist/src/run.js +1 -0
  38. package/dist/src/run.js.map +1 -1
  39. package/dist/src/sveltekit/sveltekit-wizard.js +5 -3
  40. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  41. package/dist/src/utils/clack-utils.d.ts +3 -1
  42. package/dist/src/utils/clack-utils.js +18 -12
  43. package/dist/src/utils/clack-utils.js.map +1 -1
  44. package/dist/src/utils/package-manager.d.ts +1 -0
  45. package/dist/src/utils/package-manager.js +5 -0
  46. package/dist/src/utils/package-manager.js.map +1 -1
  47. package/dist/src/utils/types.d.ts +9 -0
  48. package/dist/src/utils/types.js.map +1 -1
  49. package/dist/test/apple/cocoapod.test.d.ts +1 -0
  50. package/dist/test/apple/cocoapod.test.js +409 -0
  51. package/dist/test/apple/cocoapod.test.js.map +1 -0
  52. package/dist/test/apple/code-tools.test.d.ts +1 -0
  53. package/dist/test/apple/code-tools.test.js +673 -0
  54. package/dist/test/apple/code-tools.test.js.map +1 -0
  55. package/dist/test/apple/fastfile.test.d.ts +1 -0
  56. package/dist/test/apple/fastfile.test.js +431 -0
  57. package/dist/test/apple/fastfile.test.js.map +1 -0
  58. package/dist/test/apple/templates.test.d.ts +1 -0
  59. package/dist/test/apple/templates.test.js +73 -0
  60. package/dist/test/apple/templates.test.js.map +1 -0
  61. package/dist/test/apple/xcode-manager.test.d.ts +1 -0
  62. package/dist/test/apple/xcode-manager.test.js +834 -0
  63. package/dist/test/apple/xcode-manager.test.js.map +1 -0
  64. package/dist/test/utils/clack-utils.test.js +89 -0
  65. package/dist/test/utils/clack-utils.test.js.map +1 -1
  66. package/e2e-tests/jest.config.ts +1 -0
  67. package/e2e-tests/test-applications/apple/damaged-missing-configuration-list/Project.xcodeproj/project.pbxproj +52 -0
  68. package/e2e-tests/test-applications/apple/damaged-missing-configuration-list/Project.xcodeproj/xcshareddata/xcschemes/Project1.xcscheme +78 -0
  69. package/e2e-tests/test-applications/apple/no-targets/Project.xcodeproj/project.pbxproj +62 -0
  70. package/e2e-tests/test-applications/apple/no-targets/Project.xcodeproj/xcshareddata/xcschemes/Project1.xcscheme +78 -0
  71. package/e2e-tests/test-applications/apple/spm-swiftui-multi-targets/Project.xcodeproj/project.pbxproj +470 -0
  72. package/e2e-tests/test-applications/apple/spm-swiftui-multi-targets/Project.xcodeproj/xcshareddata/xcschemes/Project1.xcscheme +78 -0
  73. package/e2e-tests/test-applications/apple/spm-swiftui-multi-targets/Project1/ContentView.swift +7 -0
  74. package/e2e-tests/test-applications/apple/spm-swiftui-multi-targets/Project1/Project1App.swift +10 -0
  75. package/e2e-tests/test-applications/apple/spm-swiftui-multi-targets/Project2/ContentView.swift +7 -0
  76. package/e2e-tests/test-applications/apple/spm-swiftui-multi-targets/Project2/Project2App.swift +10 -0
  77. package/e2e-tests/test-applications/apple/spm-swiftui-single-target/Project.xcodeproj/project.pbxproj +382 -0
  78. package/e2e-tests/test-applications/apple/spm-swiftui-single-target/Project.xcodeproj/xcshareddata/xcschemes/Project.xcscheme +78 -0
  79. package/e2e-tests/test-applications/apple/spm-swiftui-single-target/Sources/ContentView.swift +7 -0
  80. package/e2e-tests/test-applications/apple/spm-swiftui-single-target/Sources/MainApp.swift +10 -0
  81. package/package.json +3 -2
  82. package/src/apple/apple-wizard.ts +1 -2
  83. package/src/apple/code-tools.ts +21 -6
  84. package/src/apple/fastlane.ts +18 -2
  85. package/src/apple/templates.ts +2 -2
  86. package/src/apple/xcode-manager.ts +181 -94
  87. package/src/nextjs/nextjs-wizard.ts +5 -2
  88. package/src/nuxt/nuxt-wizard.ts +6 -3
  89. package/src/nuxt/sdk-setup.ts +2 -0
  90. package/src/react-native/react-native-wizard.ts +5 -2
  91. package/src/remix/remix-wizard.ts +5 -2
  92. package/src/run.ts +2 -0
  93. package/src/sveltekit/sveltekit-wizard.ts +5 -2
  94. package/src/utils/clack-utils.ts +12 -2
  95. package/src/utils/package-manager.ts +6 -0
  96. package/src/utils/types.ts +10 -0
  97. package/test/apple/cocoapod.test.ts +306 -0
  98. package/test/apple/code-tools.test.ts +1042 -0
  99. package/test/apple/fastfile.test.ts +550 -0
  100. package/test/apple/templates.test.ts +191 -0
  101. package/test/apple/xcode-manager.test.ts +1066 -0
  102. package/test/utils/clack-utils.test.ts +92 -0
  103. package/types/xcode.d.ts +526 -0
@@ -0,0 +1,1042 @@
1
+ import * as Sentry from '@sentry/node';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ import {
6
+ addCodeSnippetToProject,
7
+ exportForTesting,
8
+ } from '../../src/apple/code-tools';
9
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
10
+ import * as clack from '@clack/prompts';
11
+
12
+ // Test Constants
13
+ const invalidAppDelegateSwift = `func application() {}`;
14
+ const validAppDelegateSwift = `
15
+ import UIKit
16
+
17
+ @main
18
+ class AppDelegate: UIResponder, UIApplicationDelegate {
19
+
20
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
21
+ // Override point for customization after application launch.
22
+ return true
23
+ }
24
+ }`;
25
+ const validAppDelegateSwiftWithSentry = `
26
+ import UIKit
27
+ import Sentry
28
+
29
+
30
+ @main
31
+ class AppDelegate: UIResponder, UIApplicationDelegate {
32
+
33
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
34
+ SentrySDK.start { options in
35
+ options.dsn = "https://example.com/sentry-dsn"
36
+ options.debug = true // Enabled debug when first installing is always helpful
37
+ // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
38
+ // We recommend adjusting this value in production.
39
+ options.tracesSampleRate = 1.0
40
+
41
+ // Sample rate for profiling, applied on top of TracesSampleRate.
42
+ // We recommend adjusting this value in production.
43
+ options.profilesSampleRate = 1.0
44
+
45
+ // Uncomment the following lines to add more data to your events
46
+ // options.attachScreenshot = true // This adds a screenshot to the error events
47
+ // options.attachViewHierarchy = true // This adds the view hierarchy to the error events
48
+ }
49
+ // Remove the next line after confirming that your Sentry integration is working.
50
+ SentrySDK.capture(message: "This app uses Sentry! :)")
51
+
52
+ // Override point for customization after application launch.
53
+ return true
54
+ }
55
+ }`;
56
+ const invalidAppDelegateObjC = `
57
+ - (BOOL)application:(UIApplication *) {
58
+ return NO;
59
+ }`;
60
+ const validAppDelegateObjC = `
61
+ #import "AppDelegate.h"
62
+
63
+ @interface AppDelegate ()
64
+
65
+ @end
66
+
67
+ @implementation AppDelegate
68
+
69
+ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
70
+ // Override point for customization after application launch.
71
+ return YES;
72
+ }
73
+
74
+ @end`;
75
+ const validAppDelegateObjCWithSentry = `@import Sentry;
76
+
77
+ #import "AppDelegate.h"
78
+
79
+ @interface AppDelegate ()
80
+
81
+ @end
82
+
83
+ @implementation AppDelegate
84
+
85
+ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
86
+ [SentrySDK startWithConfigureOptions:^(SentryOptions * options) {
87
+ options.dsn = @"https://example.com/sentry-dsn";
88
+ options.debug = YES; // Enabled debug when first installing is always helpful
89
+ // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
90
+ // We recommend adjusting this value in production.
91
+ options.tracesSampleRate = @1.0;
92
+
93
+ // Sample rate for profiling, applied on top of TracesSampleRate.
94
+ // We recommend adjusting this value in production.
95
+ options.profilesSampleRate = @1.0;
96
+
97
+ //Uncomment the following lines to add more data to your events
98
+ //options.attachScreenshot = YES; //This will add a screenshot to the error events
99
+ //options.attachViewHierarchy = YES; //This will add the view hierarchy to the error events
100
+ }];
101
+ //Remove the next line after confirming that your Sentry integration is working.
102
+ [SentrySDK captureMessage:@"This app uses Sentry!"];
103
+
104
+ // Override point for customization after application launch.
105
+ return YES;
106
+ }
107
+
108
+ @end`;
109
+ const invalidAppDelegateSwiftUI = `
110
+ struct MyApp: App {
111
+ var body: some Scene {
112
+ WindowGroup { Text("Hello, world!") }
113
+ }
114
+ }`;
115
+ const validAppDelegateSwiftUI = `
116
+ import SwiftUI
117
+
118
+ @main
119
+ struct TestApp: App {
120
+ var body: some Scene {
121
+ WindowGroup {
122
+ ContentView()
123
+ }
124
+ }
125
+ }`;
126
+ const validAppDelegateSwiftUIWithSentry = `
127
+ import SwiftUI
128
+ import Sentry
129
+
130
+
131
+ @main
132
+ struct TestApp: App {
133
+ init() {
134
+ SentrySDK.start { options in
135
+ options.dsn = "https://example.com/sentry-dsn"
136
+ options.debug = true // Enabled debug when first installing is always helpful
137
+ // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
138
+ // We recommend adjusting this value in production.
139
+ options.tracesSampleRate = 1.0
140
+
141
+ // Sample rate for profiling, applied on top of TracesSampleRate.
142
+ // We recommend adjusting this value in production.
143
+ options.profilesSampleRate = 1.0
144
+
145
+ // Uncomment the following lines to add more data to your events
146
+ // options.attachScreenshot = true // This adds a screenshot to the error events
147
+ // options.attachViewHierarchy = true // This adds the view hierarchy to the error events
148
+ }
149
+ // Remove the next line after confirming that your Sentry integration is working.
150
+ SentrySDK.capture(message: "This app uses Sentry! :)")
151
+ }
152
+ var body: some Scene {
153
+ WindowGroup {
154
+ ContentView()
155
+ }
156
+ }
157
+ }`;
158
+
159
+ const prepareTempDir = (): string => {
160
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-tools-test'));
161
+ return tempDir;
162
+ };
163
+
164
+ const prepareAppDelegateFile = (
165
+ dir: string,
166
+ content: string,
167
+ ext: 'm' | 'mm' | 'swift',
168
+ ): string => {
169
+ const filePath = path.join(dir, `AppDelegate.${ext}`);
170
+ fs.writeFileSync(filePath, content, 'utf8');
171
+ return filePath;
172
+ };
173
+
174
+ const dsn = 'https://example.com/sentry-dsn';
175
+
176
+ // Mock Setup
177
+
178
+ jest.mock('../../src/utils/bash');
179
+ jest.spyOn(Sentry, 'setTag').mockImplementation();
180
+ jest.spyOn(Sentry, 'captureException').mockImplementation();
181
+
182
+ // Test Suite
183
+
184
+ describe('code-tools', () => {
185
+ beforeEach(() => {
186
+ jest.spyOn(clack.log, 'info').mockImplementation();
187
+ });
188
+
189
+ afterEach(() => {
190
+ jest.clearAllMocks();
191
+ });
192
+
193
+ describe('#isAppDelegateFile', () => {
194
+ const prepareTestFile = (
195
+ content: string,
196
+ ext: 'm' | 'mm' | 'swift',
197
+ ): string => {
198
+ const tempDir = prepareTempDir();
199
+ return prepareAppDelegateFile(tempDir, content, ext);
200
+ };
201
+
202
+ describe('swift files', () => {
203
+ describe('swift app launch regex', () => {
204
+ describe('valid cases', () => {
205
+ const variations: {
206
+ name: string;
207
+ code: string;
208
+ }[] = [
209
+ {
210
+ name: 'with underscores',
211
+ code: 'func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {',
212
+ },
213
+ {
214
+ name: 'with different dictionary type',
215
+ code: 'func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {',
216
+ },
217
+ {
218
+ name: 'with extra whitespace',
219
+ code: ' func application ( _ application: UIApplication , didFinishLaunchingWithOptions launchOptions: [ NSObject : AnyObject ]? ) -> Bool { ',
220
+ },
221
+ {
222
+ name: 'macOS notification variant',
223
+ code: 'func applicationDidFinishLaunching(_ aNotification: Notification) {',
224
+ },
225
+ {
226
+ name: 'macOS with extra whitespace',
227
+ code: 'func applicationDidFinishLaunching ( _ aNotification: Notification ) {',
228
+ },
229
+ ];
230
+
231
+ for (const variation of variations) {
232
+ describe(`${variation.name}`, () => {
233
+ it(`should return true`, () => {
234
+ // -- Arrange --
235
+ const filePath = prepareTestFile(variation.code, 'swift');
236
+
237
+ // -- Act --
238
+ const result = exportForTesting.isAppDelegateFile(filePath);
239
+
240
+ // -- Assert --
241
+ expect(result).toBeTruthy();
242
+ });
243
+ });
244
+ }
245
+
246
+ describe('invalid cases', () => {
247
+ const variations: {
248
+ name: string;
249
+ code: string;
250
+ }[] = [
251
+ {
252
+ name: 'missing application method',
253
+ code: 'import UIKit',
254
+ },
255
+ {
256
+ name: 'typo in method name',
257
+ code: 'func applicatioM(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {',
258
+ },
259
+ {
260
+ name: 'garbage input',
261
+ code: 'asdf;jk23;uas()d{',
262
+ },
263
+ ];
264
+
265
+ for (const variation of variations) {
266
+ describe(`${variation.name}`, () => {
267
+ it('should return false', () => {
268
+ // -- Arrange --
269
+ const filePath = prepareTestFile(variation.code, 'swift');
270
+
271
+ // -- Act --
272
+ const result = exportForTesting.isAppDelegateFile(filePath);
273
+
274
+ // -- Assert --
275
+ expect(result).toBeFalsy();
276
+ });
277
+ });
278
+ }
279
+ });
280
+ });
281
+ });
282
+ });
283
+
284
+ describe('objc files', () => {
285
+ describe('valid cases', () => {
286
+ const variations: {
287
+ name: string;
288
+ code: string;
289
+ }[] = [
290
+ {
291
+ name: 'basic',
292
+ code: '- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {',
293
+ },
294
+ {
295
+ name: 'with more whitespace',
296
+ code: '- ( BOOL ) application: ( UIApplication * ) application didFinishLaunchingWithOptions: ( NSDictionary * ) launchOptions {',
297
+ },
298
+ ];
299
+
300
+ for (const variation of variations) {
301
+ describe(`${variation.name}`, () => {
302
+ it('should return true', () => {
303
+ // -- Arrange --
304
+ const filePath = prepareTestFile(variation.code, 'm');
305
+
306
+ // -- Act --
307
+ const result = exportForTesting.isAppDelegateFile(filePath);
308
+
309
+ // -- Assert --
310
+ expect(result).toBeTruthy();
311
+ });
312
+ });
313
+ }
314
+ });
315
+
316
+ describe('invalid cases', () => {
317
+ const variations: {
318
+ name: string;
319
+ code: string;
320
+ }[] = [
321
+ {
322
+ name: 'missing application method',
323
+ code: 'import UIKit',
324
+ },
325
+ ];
326
+
327
+ for (const variation of variations) {
328
+ describe(`${variation.name}`, () => {
329
+ it('should return false', () => {
330
+ // -- Arrange --
331
+ const filePath = prepareTestFile(variation.code, 'm');
332
+
333
+ // -- Act --
334
+ const result = exportForTesting.isAppDelegateFile(filePath);
335
+
336
+ // -- Assert --
337
+ expect(result).toBeFalsy();
338
+ });
339
+ });
340
+ }
341
+ });
342
+ });
343
+
344
+ describe('swiftui files', () => {
345
+ describe('valid cases', () => {
346
+ const variations: {
347
+ name: string;
348
+ code: string;
349
+ }[] = [
350
+ {
351
+ name: 'basic',
352
+ code: '@main struct MyApp: App {',
353
+ },
354
+ {
355
+ name: 'with more whitespace',
356
+ code: '@main struct MyApp: App {',
357
+ },
358
+ {
359
+ name: 'with SwiftUI namespace',
360
+ code: '@main struct App: SwiftUI.App {',
361
+ },
362
+ ];
363
+
364
+ for (const variation of variations) {
365
+ describe(`${variation.name}`, () => {
366
+ it('should return true', () => {
367
+ // -- Arrange --
368
+ const filePath = prepareTestFile(variation.code, 'swift');
369
+
370
+ // -- Act --
371
+ const result = exportForTesting.isAppDelegateFile(filePath);
372
+
373
+ // -- Assert --
374
+ expect(result).toBeTruthy();
375
+ });
376
+ });
377
+ }
378
+ });
379
+
380
+ describe('invalid cases', () => {
381
+ const variations: {
382
+ name: string;
383
+ code: string;
384
+ }[] = [
385
+ {
386
+ name: 'missing @main',
387
+ code: 'struct App: App {',
388
+ },
389
+ {
390
+ name: 'missing super-type App',
391
+ code: 'struct MyApp {',
392
+ },
393
+ {
394
+ name: 'imported not from SwiftUI',
395
+ code: '@main struct App: MySwiftyUI.App {',
396
+ },
397
+ {
398
+ name: 'imported not from SwiftUI but similar',
399
+ code: '@main struct App: MySwiftUI.App {',
400
+ },
401
+ ];
402
+
403
+ for (const variation of variations) {
404
+ describe(`${variation.name}`, () => {
405
+ it('should return false', () => {
406
+ // -- Arrange --
407
+ const filePath = prepareTestFile(variation.code, 'swift');
408
+
409
+ // -- Act --
410
+ const result = exportForTesting.isAppDelegateFile(filePath);
411
+
412
+ // -- Assert --
413
+ expect(result).toBeFalsy();
414
+ });
415
+ });
416
+ }
417
+ });
418
+ });
419
+
420
+ describe('file not found', () => {
421
+ it('should throw an error', () => {
422
+ // -- Arrange --
423
+ const invalidPath = path.join(os.tmpdir(), 'invalid-path');
424
+
425
+ // -- Act & Assert --
426
+ expect(() => exportForTesting.isAppDelegateFile(invalidPath)).toThrow();
427
+ });
428
+ });
429
+ });
430
+
431
+ describe('#findAppDidFinishLaunchingWithOptions', () => {
432
+ describe('no files given', () => {
433
+ it('should check files in directory', () => {
434
+ // -- Arrange --
435
+ const tempDir = prepareTempDir();
436
+ const filePath = prepareAppDelegateFile(
437
+ tempDir,
438
+ validAppDelegateSwift,
439
+ 'swift',
440
+ );
441
+
442
+ // -- Act --
443
+ const result =
444
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
445
+
446
+ // -- Assert --
447
+ expect(result).toBe(filePath);
448
+ });
449
+ });
450
+
451
+ describe('SwiftUI file found', () => {
452
+ describe('is app delegate', () => {
453
+ it('should return the file path', () => {
454
+ // -- Arrange --
455
+ const tempDir = prepareTempDir();
456
+ const filePath = prepareAppDelegateFile(
457
+ tempDir,
458
+ validAppDelegateSwiftUI,
459
+ 'swift',
460
+ );
461
+
462
+ // -- Act --
463
+ const result =
464
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
465
+
466
+ // -- Assert --
467
+ expect(result).toBe(filePath);
468
+ });
469
+ });
470
+
471
+ describe('is not app delegate', () => {
472
+ it('should be ignored', () => {
473
+ // -- Arrange --
474
+ const tempDir = prepareTempDir();
475
+ prepareAppDelegateFile(tempDir, invalidAppDelegateSwiftUI, 'swift');
476
+
477
+ // -- Act --
478
+ const result =
479
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
480
+
481
+ // -- Assert --
482
+ expect(result).toBeNull();
483
+ });
484
+ });
485
+ });
486
+
487
+ describe('Swift file found', () => {
488
+ describe('is app delegate', () => {
489
+ it('should return the file path', () => {
490
+ // -- Arrange --
491
+ const tempDir = prepareTempDir();
492
+ const filePath = prepareAppDelegateFile(
493
+ tempDir,
494
+ validAppDelegateSwift,
495
+ 'swift',
496
+ );
497
+
498
+ // -- Act --
499
+ const result =
500
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
501
+
502
+ // -- Assert --
503
+ expect(result).toBe(filePath);
504
+ });
505
+ });
506
+
507
+ describe('is not app delegate', () => {
508
+ it('should be ignored', () => {
509
+ // -- Arrange --
510
+ const tempDir = prepareTempDir();
511
+ prepareAppDelegateFile(tempDir, invalidAppDelegateSwift, 'swift');
512
+
513
+ // -- Act --
514
+ const result =
515
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
516
+
517
+ // -- Assert --
518
+ expect(result).toBeNull();
519
+ });
520
+ });
521
+ });
522
+
523
+ describe('Objective-C file found', () => {
524
+ describe('is app delegate', () => {
525
+ it('should return the file path', () => {
526
+ // -- Arrange --
527
+ const tempDir = prepareTempDir();
528
+ const filePath = prepareAppDelegateFile(
529
+ tempDir,
530
+ validAppDelegateObjC,
531
+ 'm',
532
+ );
533
+
534
+ // -- Act --
535
+ const result =
536
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
537
+
538
+ // -- Assert --
539
+ expect(result).toBe(filePath);
540
+ });
541
+ });
542
+
543
+ describe('is not app delegate', () => {
544
+ it('should be ignored', () => {
545
+ // -- Arrange --
546
+ const tempDir = prepareTempDir();
547
+ prepareAppDelegateFile(tempDir, invalidAppDelegateObjC, 'm');
548
+
549
+ // -- Act --
550
+ const result =
551
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
552
+
553
+ // -- Assert --
554
+ expect(result).toBeNull();
555
+ });
556
+ });
557
+ });
558
+
559
+ describe('Objective-C++ file found', () => {
560
+ describe('is app delegate', () => {
561
+ it('should return the file path', () => {
562
+ // -- Arrange --
563
+ const tempDir = prepareTempDir();
564
+ const filePath = prepareAppDelegateFile(
565
+ tempDir,
566
+ validAppDelegateObjC,
567
+ 'mm',
568
+ );
569
+
570
+ // -- Act --
571
+ const result = exportForTesting.findAppDidFinishLaunchingWithOptions(
572
+ tempDir,
573
+ [filePath],
574
+ );
575
+
576
+ // -- Assert --
577
+ expect(result).toBe(filePath);
578
+ });
579
+ });
580
+
581
+ describe('is not app delegate', () => {
582
+ it('should be ignored', () => {
583
+ // -- Arrange --
584
+ const tempDir = prepareTempDir();
585
+ prepareAppDelegateFile(tempDir, invalidAppDelegateObjC, 'mm');
586
+
587
+ // -- Act --
588
+ const result =
589
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
590
+
591
+ // -- Assert --
592
+ expect(result).toBeNull();
593
+ });
594
+ });
595
+ });
596
+
597
+ describe('file in list not found', () => {
598
+ it('should return null', () => {
599
+ // -- Arrange --
600
+ const tempDir = prepareTempDir();
601
+ const filePath = prepareAppDelegateFile(
602
+ tempDir,
603
+ invalidAppDelegateSwift,
604
+ 'swift',
605
+ );
606
+
607
+ // -- Act --
608
+ const result = exportForTesting.findAppDidFinishLaunchingWithOptions(
609
+ tempDir,
610
+ [filePath],
611
+ );
612
+
613
+ // -- Assert --
614
+ expect(result).toBeNull();
615
+ });
616
+ });
617
+
618
+ describe('unrelated file found', () => {
619
+ it('should be ignored', () => {
620
+ // -- Arrange --
621
+ const tempDir = prepareTempDir();
622
+ const filePath = prepareAppDelegateFile(
623
+ tempDir,
624
+ invalidAppDelegateSwift,
625
+ 'swift',
626
+ );
627
+
628
+ // -- Act --
629
+ const result = exportForTesting.findAppDidFinishLaunchingWithOptions(
630
+ tempDir,
631
+ [filePath],
632
+ );
633
+
634
+ // -- Assert --
635
+ expect(result).toBeNull();
636
+ });
637
+ });
638
+
639
+ describe('directory in list', () => {
640
+ describe('name starts with dot', () => {
641
+ it('should be ignored', () => {
642
+ // -- Arrange --
643
+ const tempDir = prepareTempDir();
644
+
645
+ const hiddenDir = path.join(tempDir, '.hidden');
646
+ fs.mkdirSync(hiddenDir);
647
+
648
+ prepareAppDelegateFile(hiddenDir, validAppDelegateSwift, 'swift');
649
+
650
+ // -- Act --
651
+ const result =
652
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
653
+
654
+ // -- Assert --
655
+ expect(result).toBeNull();
656
+ });
657
+ });
658
+
659
+ describe('name ends with .xcodeproj', () => {
660
+ it('should be ignored', () => {
661
+ // -- Arrange --
662
+ const tempDir = prepareTempDir();
663
+ const xcodeDir = path.join(tempDir, 'MyProject.xcodeproj');
664
+ fs.mkdirSync(xcodeDir);
665
+
666
+ prepareAppDelegateFile(xcodeDir, validAppDelegateSwift, 'swift');
667
+
668
+ // -- Act --
669
+ const result =
670
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
671
+
672
+ // -- Assert --
673
+ expect(result).toBeNull();
674
+ });
675
+ });
676
+
677
+ describe('name ends with .xcassets', () => {
678
+ it('should be ignored', () => {
679
+ // -- Arrange --
680
+ const tempDir = prepareTempDir();
681
+ const xcassetsDir = path.join(tempDir, 'MyProject.xcassets');
682
+ fs.mkdirSync(xcassetsDir);
683
+
684
+ prepareAppDelegateFile(xcassetsDir, validAppDelegateSwift, 'swift');
685
+
686
+ // -- Act --
687
+ const result =
688
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
689
+
690
+ // -- Assert --
691
+ expect(result).toBeNull();
692
+ });
693
+ });
694
+
695
+ describe('is not a directory', () => {
696
+ it('should be ignored', () => {
697
+ // -- Arrange --
698
+ const tempDir = prepareTempDir();
699
+ const filePath = path.join(tempDir, 'some-file');
700
+ fs.writeFileSync(filePath, validAppDelegateSwift, 'utf8');
701
+
702
+ // -- Act --
703
+ const result =
704
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
705
+
706
+ // -- Assert --
707
+ expect(result).toBeNull();
708
+ });
709
+ });
710
+ });
711
+
712
+ describe('multiple files could be app delegate', () => {
713
+ it('should return the first one', () => {
714
+ // -- Arrange --
715
+ const tempDir = prepareTempDir();
716
+ const filePath = prepareAppDelegateFile(
717
+ tempDir,
718
+ validAppDelegateSwift,
719
+ 'swift',
720
+ );
721
+ prepareAppDelegateFile(tempDir, validAppDelegateSwift, 'swift');
722
+
723
+ // -- Act --
724
+ const result =
725
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
726
+
727
+ // -- Assert --
728
+ expect(result).toBe(filePath);
729
+ });
730
+ });
731
+
732
+ describe('multiple nested directories with app delegate', () => {
733
+ it('should return the first one', () => {
734
+ // -- Arrange --
735
+ const tempDir = prepareTempDir();
736
+
737
+ const nestedDir = path.join(tempDir, 'nested');
738
+ fs.mkdirSync(nestedDir);
739
+ const nestedFilePath = prepareAppDelegateFile(
740
+ nestedDir,
741
+ validAppDelegateSwift,
742
+ 'swift',
743
+ );
744
+
745
+ const nestedDir2 = path.join(tempDir, 'nested2');
746
+ fs.mkdirSync(nestedDir2);
747
+ prepareAppDelegateFile(nestedDir2, validAppDelegateSwift, 'swift');
748
+
749
+ // -- Act --
750
+ const result =
751
+ exportForTesting.findAppDidFinishLaunchingWithOptions(tempDir);
752
+
753
+ // -- Assert --
754
+ expect(result).toBe(nestedFilePath);
755
+ });
756
+ });
757
+
758
+ describe('no app delegate found', () => {
759
+ it('should return null', () => {
760
+ // -- Arrange --
761
+ const tempDir = fs.mkdtempSync(
762
+ path.join(os.tmpdir(), 'code-tools-test'),
763
+ );
764
+
765
+ // -- Act --
766
+ const result = exportForTesting.findAppDidFinishLaunchingWithOptions(
767
+ tempDir,
768
+ [],
769
+ );
770
+
771
+ // -- Assert --
772
+ expect(result).toBeNull();
773
+ });
774
+ });
775
+ });
776
+
777
+ describe('#addCodeSnippetToProject', () => {
778
+ describe('app delegate file is not found', () => {
779
+ it('should return false', () => {
780
+ // -- Arrange --
781
+ const tempDir = prepareTempDir();
782
+
783
+ // -- Act --
784
+ const result = addCodeSnippetToProject(
785
+ tempDir,
786
+ ['AppDelegate.swift'],
787
+ 'https://example.com/sentry-dsn',
788
+ );
789
+
790
+ // -- Assert --
791
+ expect(result).toBeFalsy();
792
+ });
793
+ });
794
+
795
+ describe('app delegate file is found', () => {
796
+ let tempDir: string;
797
+ let appDelegatePath: string;
798
+
799
+ beforeEach(() => {
800
+ // -- Arrange --
801
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-tools-test'));
802
+ appDelegatePath = path.join(tempDir, 'AppDelegate.swift');
803
+ fs.writeFileSync(appDelegatePath, validAppDelegateSwift, 'utf8');
804
+ });
805
+
806
+ describe('is Swift file', () => {
807
+ describe('Sentry is not initialized', () => {
808
+ let tempDir: string;
809
+ let filePath: string;
810
+
811
+ beforeEach(() => {
812
+ tempDir = prepareTempDir();
813
+ filePath = prepareAppDelegateFile(
814
+ tempDir,
815
+ validAppDelegateSwift,
816
+ 'swift',
817
+ );
818
+ });
819
+
820
+ it('should add the code snippet', () => {
821
+ // -- Act --
822
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
823
+
824
+ // -- Assert --
825
+ expect(result).toBeTruthy();
826
+ const modifiedFileContent = fs.readFileSync(filePath, 'utf8');
827
+ expect(modifiedFileContent).toBe(validAppDelegateSwiftWithSentry);
828
+ });
829
+
830
+ it("should set tag 'code-language'", () => {
831
+ // -- Act --
832
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
833
+
834
+ // -- Assert --
835
+ expect(result).toBeTruthy();
836
+ expect(Sentry.setTag).toHaveBeenCalledWith(
837
+ 'code-language',
838
+ 'swift',
839
+ );
840
+ });
841
+
842
+ it("should set tag 'ui-engine'", () => {
843
+ // -- Act --
844
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
845
+
846
+ // -- Assert --
847
+ expect(result).toBeTruthy();
848
+ expect(Sentry.setTag).toHaveBeenCalledWith('ui-engine', 'uikit');
849
+ });
850
+ });
851
+
852
+ describe('Sentry is already initialized', () => {
853
+ it('should not add the code snippet', () => {
854
+ // -- Arrange --
855
+ const tempDir = prepareTempDir();
856
+ const filePath = prepareAppDelegateFile(
857
+ tempDir,
858
+ validAppDelegateSwiftWithSentry,
859
+ 'swift',
860
+ );
861
+
862
+ // -- Act --
863
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
864
+
865
+ // -- Assert --
866
+ expect(result).toBeTruthy();
867
+ const modifiedFileContent = fs.readFileSync(filePath, 'utf8');
868
+ expect(modifiedFileContent).toBe(validAppDelegateSwiftWithSentry);
869
+ });
870
+ });
871
+
872
+ describe('is SwiftUI file', () => {
873
+ describe('Sentry is not initialized', () => {
874
+ let tempDir: string;
875
+ let filePath: string;
876
+
877
+ beforeEach(() => {
878
+ tempDir = prepareTempDir();
879
+ filePath = prepareAppDelegateFile(
880
+ tempDir,
881
+ validAppDelegateSwiftUI,
882
+ 'swift',
883
+ );
884
+ });
885
+
886
+ it('should add the code snippet', () => {
887
+ // -- Act --
888
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
889
+
890
+ // -- Assert --
891
+ expect(result).toBeTruthy();
892
+ const modifiedFileContent = fs.readFileSync(filePath, 'utf8');
893
+ expect(modifiedFileContent).toBe(
894
+ validAppDelegateSwiftUIWithSentry,
895
+ );
896
+ });
897
+
898
+ it("should set tag 'code-language'", () => {
899
+ // -- Act --
900
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
901
+
902
+ // -- Assert --
903
+ expect(result).toBeTruthy();
904
+ expect(Sentry.setTag).toHaveBeenNthCalledWith(
905
+ 1,
906
+ 'code-language',
907
+ 'swift',
908
+ );
909
+ });
910
+
911
+ it("should set tag 'ui-engine'", () => {
912
+ // -- Act --
913
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
914
+
915
+ // -- Assert --
916
+ expect(result).toBeTruthy();
917
+ expect(Sentry.setTag).toHaveBeenNthCalledWith(
918
+ 2,
919
+ 'ui-engine',
920
+ 'swiftui',
921
+ );
922
+ });
923
+ });
924
+
925
+ describe('Sentry is already initialized', () => {
926
+ it('should not add the code snippet', () => {
927
+ // -- Arrange --
928
+ const tempDir = prepareTempDir();
929
+ const filePath = prepareAppDelegateFile(
930
+ tempDir,
931
+ validAppDelegateSwiftUIWithSentry,
932
+ 'swift',
933
+ );
934
+
935
+ // -- Act --
936
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
937
+
938
+ // -- Assert --
939
+ expect(result).toBeTruthy();
940
+ const modifiedFileContent = fs.readFileSync(filePath, 'utf8');
941
+ expect(modifiedFileContent).toBe(
942
+ validAppDelegateSwiftUIWithSentry,
943
+ );
944
+ });
945
+ });
946
+ });
947
+
948
+ describe('is not matching SwiftUI regex', () => {
949
+ it('should not add the code snippet', () => {
950
+ // -- Arrange --
951
+ const tempDir = prepareTempDir();
952
+ const filePath = prepareAppDelegateFile(
953
+ tempDir,
954
+ invalidAppDelegateSwiftUI,
955
+ 'swift',
956
+ );
957
+
958
+ // -- Act --
959
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
960
+
961
+ // -- Assert --
962
+ expect(result).toBeFalsy();
963
+ });
964
+ });
965
+ });
966
+
967
+ describe('is Objective-C file', () => {
968
+ describe('Sentry is not initialized', () => {
969
+ it('should add the code snippet', () => {
970
+ // -- Act --
971
+ const tempDir = prepareTempDir();
972
+ const filePath = prepareAppDelegateFile(
973
+ tempDir,
974
+ validAppDelegateObjC,
975
+ 'm',
976
+ );
977
+
978
+ // -- Act --
979
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
980
+
981
+ // -- Assert --
982
+ expect(result).toBeTruthy();
983
+ const modifiedFileContent = fs.readFileSync(filePath, 'utf8');
984
+ expect(modifiedFileContent).toBe(validAppDelegateObjCWithSentry);
985
+ });
986
+ });
987
+
988
+ describe('Sentry is already initialized', () => {
989
+ let tempDir: string;
990
+ let filePath: string;
991
+
992
+ beforeEach(() => {
993
+ tempDir = prepareTempDir();
994
+ filePath = prepareAppDelegateFile(
995
+ tempDir,
996
+ validAppDelegateObjCWithSentry,
997
+ 'm',
998
+ );
999
+ });
1000
+
1001
+ it('should not add the code snippet', () => {
1002
+ // -- Act --
1003
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
1004
+
1005
+ // -- Assert --
1006
+ expect(result).toBeTruthy();
1007
+ const modifiedFileContent = fs.readFileSync(filePath, 'utf8');
1008
+ expect(modifiedFileContent).toBe(validAppDelegateObjCWithSentry);
1009
+ });
1010
+
1011
+ it('should log info', () => {
1012
+ // -- Act --
1013
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
1014
+
1015
+ // -- Assert --
1016
+ expect(result).toBeTruthy();
1017
+ expect(clack.log.info).toHaveBeenCalledWith(
1018
+ 'Sentry is already initialized in your AppDelegate. Skipping adding the code snippet.',
1019
+ );
1020
+ });
1021
+ });
1022
+
1023
+ it("should set tag 'code-language'", () => {
1024
+ // -- Arrange --
1025
+ const tempDir = prepareTempDir();
1026
+ const filePath = prepareAppDelegateFile(
1027
+ tempDir,
1028
+ validAppDelegateObjC,
1029
+ 'm',
1030
+ );
1031
+
1032
+ // -- Act --
1033
+ const result = addCodeSnippetToProject(tempDir, [filePath], dsn);
1034
+
1035
+ // -- Assert --
1036
+ expect(result).toBeTruthy();
1037
+ expect(Sentry.setTag).toHaveBeenCalledWith('code-language', 'objc');
1038
+ });
1039
+ });
1040
+ });
1041
+ });
1042
+ });