@sentry/wizard 2.6.1 → 3.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 (124) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/dist/NextJs/configs/next.config.template.js +1 -1
  3. package/dist/bin.js +7 -3
  4. package/dist/bin.js.map +1 -1
  5. package/dist/index.js +7 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/Constants.js +1 -1
  8. package/dist/lib/Constants.js.map +1 -1
  9. package/dist/lib/Helper/BottomBar.js +2 -2
  10. package/dist/lib/Helper/BottomBar.js.map +1 -1
  11. package/dist/lib/Helper/File.js +10 -12
  12. package/dist/lib/Helper/File.js.map +1 -1
  13. package/dist/lib/Helper/Logging.js +1 -1
  14. package/dist/lib/Helper/Logging.js.map +1 -1
  15. package/dist/lib/Helper/Package.d.ts +1 -0
  16. package/dist/lib/Helper/Package.js +46 -0
  17. package/dist/lib/Helper/Package.js.map +1 -0
  18. package/dist/lib/Helper/PackageManager.d.ts +22 -0
  19. package/dist/lib/Helper/PackageManager.js +132 -0
  20. package/dist/lib/Helper/PackageManager.js.map +1 -0
  21. package/dist/lib/Helper/SentryCli.d.ts +3 -3
  22. package/dist/lib/Helper/SentryCli.js +3 -3
  23. package/dist/lib/Helper/SentryCli.js.map +1 -1
  24. package/dist/lib/Helper/Wizard.d.ts +4 -4
  25. package/dist/lib/Helper/Wizard.js +13 -12
  26. package/dist/lib/Helper/Wizard.js.map +1 -1
  27. package/dist/lib/Helper/__tests__/File.js +5 -5
  28. package/dist/lib/Helper/__tests__/File.js.map +1 -1
  29. package/dist/lib/Helper/__tests__/MergeConfig.js +29 -18
  30. package/dist/lib/Helper/__tests__/MergeConfig.js.map +1 -1
  31. package/dist/lib/Helper/__tests__/SentryCli.js.map +1 -1
  32. package/dist/lib/Setup.js +11 -9
  33. package/dist/lib/Setup.js.map +1 -1
  34. package/dist/lib/Steps/BaseStep.d.ts +2 -2
  35. package/dist/lib/Steps/BaseStep.js +3 -3
  36. package/dist/lib/Steps/BaseStep.js.map +1 -1
  37. package/dist/lib/Steps/ChooseIntegration.d.ts +1 -1
  38. package/dist/lib/Steps/ChooseIntegration.js +7 -5
  39. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  40. package/dist/lib/Steps/ConfigureProject.d.ts +1 -1
  41. package/dist/lib/Steps/ConfigureProject.js +5 -3
  42. package/dist/lib/Steps/ConfigureProject.js.map +1 -1
  43. package/dist/lib/Steps/Initial.d.ts +1 -1
  44. package/dist/lib/Steps/Initial.js +6 -4
  45. package/dist/lib/Steps/Initial.js.map +1 -1
  46. package/dist/lib/Steps/Integrations/BaseIntegration.d.ts +2 -2
  47. package/dist/lib/Steps/Integrations/BaseIntegration.js +5 -4
  48. package/dist/lib/Steps/Integrations/BaseIntegration.js.map +1 -1
  49. package/dist/lib/Steps/Integrations/Cordova.d.ts +2 -2
  50. package/dist/lib/Steps/Integrations/Cordova.js +12 -10
  51. package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
  52. package/dist/lib/Steps/Integrations/Electron.d.ts +2 -2
  53. package/dist/lib/Steps/Integrations/Electron.js +23 -21
  54. package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
  55. package/dist/lib/Steps/Integrations/MobileProject.d.ts +1 -1
  56. package/dist/lib/Steps/Integrations/MobileProject.js +7 -5
  57. package/dist/lib/Steps/Integrations/MobileProject.js.map +1 -1
  58. package/dist/lib/Steps/Integrations/NextJs.d.ts +2 -7
  59. package/dist/lib/Steps/Integrations/NextJs.js +59 -134
  60. package/dist/lib/Steps/Integrations/NextJs.js.map +1 -1
  61. package/dist/lib/Steps/Integrations/ReactNative.d.ts +9 -2
  62. package/dist/lib/Steps/Integrations/ReactNative.js +148 -78
  63. package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
  64. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js +43 -1
  65. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js.map +1 -1
  66. package/dist/lib/Steps/OpenSentry.d.ts +1 -1
  67. package/dist/lib/Steps/OpenSentry.js +19 -17
  68. package/dist/lib/Steps/OpenSentry.js.map +1 -1
  69. package/dist/lib/Steps/PromptForParameters.d.ts +1 -1
  70. package/dist/lib/Steps/PromptForParameters.js +21 -19
  71. package/dist/lib/Steps/PromptForParameters.js.map +1 -1
  72. package/dist/lib/Steps/Result.d.ts +1 -1
  73. package/dist/lib/Steps/Result.js +7 -5
  74. package/dist/lib/Steps/Result.js.map +1 -1
  75. package/dist/lib/Steps/SentryProjectSelector.d.ts +1 -1
  76. package/dist/lib/Steps/SentryProjectSelector.js +6 -4
  77. package/dist/lib/Steps/SentryProjectSelector.js.map +1 -1
  78. package/dist/lib/Steps/ShouldConfigure.d.ts +1 -1
  79. package/dist/lib/Steps/ShouldConfigure.js +5 -3
  80. package/dist/lib/Steps/ShouldConfigure.js.map +1 -1
  81. package/dist/lib/Steps/WaitForSentry.d.ts +1 -1
  82. package/dist/lib/Steps/WaitForSentry.js +11 -9
  83. package/dist/lib/Steps/WaitForSentry.js.map +1 -1
  84. package/dist/lib/Steps/Welcome.d.ts +1 -1
  85. package/dist/lib/Steps/Welcome.js +7 -6
  86. package/dist/lib/Steps/Welcome.js.map +1 -1
  87. package/dist/lib/Steps/index.js +1 -0
  88. package/dist/lib/Steps/index.js.map +1 -1
  89. package/dist/lib/__tests__/Env.js +1 -1
  90. package/dist/lib/__tests__/Env.js.map +1 -1
  91. package/dist/lib/__tests__/Setup.js +16 -1
  92. package/dist/lib/__tests__/Setup.js.map +1 -1
  93. package/lib/Helper/File.ts +2 -8
  94. package/lib/Helper/Package.ts +61 -0
  95. package/lib/Helper/PackageManager.ts +64 -0
  96. package/lib/Helper/SentryCli.ts +3 -3
  97. package/lib/Helper/Wizard.ts +7 -6
  98. package/lib/Helper/__tests__/File.ts +5 -5
  99. package/lib/Helper/__tests__/MergeConfig.ts +36 -20
  100. package/lib/Helper/__tests__/SentryCli.ts +3 -2
  101. package/lib/Helper/test-fixtures/next.config.1-merged.js +1 -1
  102. package/lib/Helper/test-fixtures/next.config.3-merged.js +1 -1
  103. package/lib/Helper/test-fixtures/next.config.4-merged.js +1 -1
  104. package/lib/Steps/BaseStep.ts +3 -3
  105. package/lib/Steps/ChooseIntegration.ts +2 -1
  106. package/lib/Steps/ConfigureProject.ts +1 -1
  107. package/lib/Steps/Initial.ts +1 -1
  108. package/lib/Steps/Integrations/BaseIntegration.ts +3 -3
  109. package/lib/Steps/Integrations/Cordova.ts +5 -5
  110. package/lib/Steps/Integrations/Electron.ts +7 -6
  111. package/lib/Steps/Integrations/MobileProject.ts +2 -1
  112. package/lib/Steps/Integrations/NextJs.ts +15 -114
  113. package/lib/Steps/Integrations/ReactNative.ts +143 -52
  114. package/lib/Steps/Integrations/__tests__/ReactNative.ts +37 -2
  115. package/lib/Steps/OpenSentry.ts +1 -1
  116. package/lib/Steps/PromptForParameters.ts +3 -2
  117. package/lib/Steps/Result.ts +1 -1
  118. package/lib/Steps/SentryProjectSelector.ts +3 -2
  119. package/lib/Steps/ShouldConfigure.ts +1 -1
  120. package/lib/Steps/WaitForSentry.ts +3 -3
  121. package/lib/Steps/Welcome.ts +1 -1
  122. package/lib/__tests__/Setup.ts +23 -0
  123. package/package.json +9 -13
  124. package/scripts/NextJs/configs/next.config.template.js +1 -1
@@ -1,21 +1,20 @@
1
1
  /* eslint-disable max-lines */
2
2
  import Chalk from 'chalk';
3
- import { exec } from 'child_process';
4
3
  import * as fs from 'fs';
5
- import { Answers, prompt } from 'inquirer';
4
+ import type { Answers } from 'inquirer';
5
+ import { prompt } from 'inquirer';
6
6
  import * as _ from 'lodash';
7
7
  import * as path from 'path';
8
- import { satisfies, subset, valid, validRange } from 'semver';
9
- import { promisify } from 'util';
10
8
 
11
- import { Args } from '../../Constants';
9
+ import type { Args } from '../../Constants';
12
10
  import { debug, green, l, nl, red } from '../../Helper/Logging';
13
11
  import { mergeConfigFile } from '../../Helper/MergeConfig';
14
- import { SentryCli, SentryCliProps } from '../../Helper/SentryCli';
12
+ import { checkPackageVersion } from '../../Helper/Package';
13
+ import { getPackageMangerChoice } from '../../Helper/PackageManager';
14
+ import type { SentryCliProps } from '../../Helper/SentryCli';
15
+ import { SentryCli } from '../../Helper/SentryCli';
15
16
  import { BaseIntegration } from './BaseIntegration';
16
17
 
17
- type PackageManager = 'yarn' | 'npm' | 'pnpm';
18
-
19
18
  const COMPATIBLE_NEXTJS_VERSIONS = '>=10.0.8 <14.0.0';
20
19
  const COMPATIBLE_SDK_VERSIONS = '>=7.3.0';
21
20
  const PROPERTIES_FILENAME = 'sentry.properties';
@@ -45,7 +44,7 @@ try {
45
44
  export class NextJs extends BaseIntegration {
46
45
  protected _sentryCli: SentryCli;
47
46
 
48
- constructor(protected _argv: Args) {
47
+ public constructor(protected _argv: Args) {
49
48
  super(_argv);
50
49
  this._sentryCli = new SentryCli(this._argv);
51
50
  }
@@ -110,24 +109,26 @@ export class NextJs extends BaseIntegration {
110
109
  nl();
111
110
 
112
111
  let userAnswers: Answers = { continue: true };
113
- const hasCompatibleNextjsVersion = this._checkPackageVersion(
112
+ const hasCompatibleNextjsVersion = checkPackageVersion(
113
+ appPackage,
114
114
  'next',
115
115
  COMPATIBLE_NEXTJS_VERSIONS,
116
116
  true,
117
117
  );
118
118
 
119
- const packageManager = this._getPackageMangerChoice();
119
+ const packageManager = getPackageMangerChoice();
120
120
  const hasSdkInstalled = this._hasPackageInstalled('@sentry/nextjs');
121
121
 
122
122
  let hasCompatibleSdkVersion = false;
123
123
  // if no package but we have nextjs, let's add it if we can
124
124
  if (!hasSdkInstalled && packageManager && hasCompatibleNextjsVersion) {
125
- await this._installPackage('@sentry/nextjs', packageManager);
125
+ await packageManager.installPackage('@sentry/nextjs');
126
126
  // can assume it's compatible since we just installed it
127
127
  hasCompatibleSdkVersion = true;
128
128
  } else {
129
129
  // otherwise, let's check the version and spit out the appropriate error
130
- hasCompatibleSdkVersion = this._checkPackageVersion(
130
+ hasCompatibleSdkVersion = checkPackageVersion(
131
+ appPackage,
131
132
  '@sentry/nextjs',
132
133
  COMPATIBLE_SDK_VERSIONS,
133
134
  true,
@@ -206,7 +207,7 @@ export class NextJs extends BaseIntegration {
206
207
  `./${PROPERTIES_FILENAME}`,
207
208
  this._sentryCli.dumpProperties(cliPropsToWrite),
208
209
  );
209
- green(`✓ Successfully created sentry.properties`);
210
+ green('✓ Successfully created sentry.properties');
210
211
  } catch {
211
212
  red(`⚠ Could not add org and project data to ${PROPERTIES_FILENAME}`);
212
213
  l(
@@ -348,106 +349,6 @@ export class NextJs extends BaseIntegration {
348
349
  return !!depsVersion || !!devDepsVersion;
349
350
  }
350
351
 
351
- private _getPackageMangerChoice(): PackageManager | null {
352
- if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) {
353
- return 'yarn';
354
- }
355
- if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) {
356
- return 'pnpm';
357
- }
358
- if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) {
359
- return 'npm';
360
- }
361
- return null;
362
- }
363
-
364
- private _getInstallCommand(packageManager: PackageManager): string {
365
- switch (packageManager) {
366
- case 'yarn':
367
- return 'yarn add';
368
- case 'pnpm':
369
- return 'pnpm add';
370
- case 'npm':
371
- return 'npm install';
372
- default:
373
- throw new Error(`Unknown package manager: ${packageManager}`);
374
- }
375
- }
376
-
377
- private async _installPackage(
378
- packageName: string,
379
- packageManager: PackageManager,
380
- ): Promise<void> {
381
- const command = this._getInstallCommand(packageManager);
382
- await promisify(exec)(`${command} ${packageName}`);
383
- green(`✓ Added \`${packageName}\` using \`${command}\`.`);
384
- return;
385
- }
386
-
387
- private _checkPackageVersion(
388
- packageName: string,
389
- acceptableVersions: string,
390
- canBeLatest: boolean,
391
- ): boolean {
392
- const depsVersion = _.get(appPackage, ['dependencies', packageName]);
393
- const devDepsVersion = _.get(appPackage, ['devDependencies', packageName]);
394
-
395
- if (!depsVersion && !devDepsVersion) {
396
- red(`✗ ${packageName} isn't in your dependencies.`);
397
- red(' Please install it with yarn/npm.');
398
- return false;
399
- } else if (
400
- !this._fulfillsVersionRange(
401
- depsVersion,
402
- acceptableVersions,
403
- canBeLatest,
404
- ) &&
405
- !this._fulfillsVersionRange(
406
- devDepsVersion,
407
- acceptableVersions,
408
- canBeLatest,
409
- )
410
- ) {
411
- red(
412
- `✗ Your \`package.json\` specifies a version of \`${packageName}\` outside of the compatible version range ${acceptableVersions}.\n`,
413
- );
414
- return false;
415
- } else {
416
- green(
417
- `✓ A compatible version of \`${packageName}\` is specified in \`package.json\`.`,
418
- );
419
- return true;
420
- }
421
- }
422
-
423
- private _fulfillsVersionRange(
424
- version: string,
425
- acceptableVersions: string,
426
- canBeLatest: boolean,
427
- ): boolean {
428
- if (version === 'latest') {
429
- return canBeLatest;
430
- }
431
-
432
- let cleanedUserVersion, isRange;
433
-
434
- if (valid(version)) {
435
- cleanedUserVersion = valid(version);
436
- isRange = false;
437
- } else if (validRange(version)) {
438
- cleanedUserVersion = validRange(version);
439
- isRange = true;
440
- }
441
-
442
- return (
443
- // If the given version is a bogus format, this will still be undefined and we'll automatically reject it
444
- !!cleanedUserVersion &&
445
- (isRange
446
- ? subset(cleanedUserVersion, acceptableVersions)
447
- : satisfies(cleanedUserVersion, acceptableVersions))
448
- );
449
- }
450
-
451
352
  private _spliceInPlace(
452
353
  arr: Array<any>,
453
354
  start: number,
@@ -1,19 +1,37 @@
1
1
  /* eslint-disable max-lines */
2
+ import { exec } from 'child_process';
2
3
  import * as fs from 'fs';
3
- import { Answers } from 'inquirer';
4
+ import type { Answers} from 'inquirer';
5
+ import { prompt } from 'inquirer';
4
6
  import * as _ from 'lodash';
5
7
  import * as path from 'path';
6
-
7
- import { Args } from '../../Constants';
8
- import { exists, matchesContent, matchFiles, patchMatchingFile } from '../../Helper/File';
9
- import { dim, green, red, yellow } from '../../Helper/Logging';
8
+ import { promisify } from 'util';
9
+
10
+ import type { Args } from '../../Constants';
11
+ import {
12
+ exists,
13
+ matchesContent,
14
+ matchFiles,
15
+ patchMatchingFile,
16
+ } from '../../Helper/File';
17
+ import { dim, green, nl, red } from '../../Helper/Logging';
18
+ import { checkPackageVersion } from '../../Helper/Package';
19
+ import { getPackageMangerChoice } from '../../Helper/PackageManager';
10
20
  import { SentryCli } from '../../Helper/SentryCli';
11
21
  import { MobileProject } from './MobileProject';
12
22
 
13
23
  const xcode = require('xcode');
14
24
 
15
- export class ReactNative extends MobileProject {
25
+ export const COMPATIBLE_REACT_NATIVE_VERSIONS = '>=0.69.0';
26
+ export const COMPATIBLE_SDK_VERSION = '>= 5.0.0';
27
+
28
+ export const SENTRY_REACT_NATIVE_PACKAGE = '@sentry/react-native';
29
+ export const REACT_NATIVE_PACKAGE = 'react-native';
16
30
 
31
+ export const DOCS_MANUAL_STEPS =
32
+ 'https://docs.sentry.io/platforms/react-native/manual-setup/manual-setup/';
33
+
34
+ export class ReactNative extends MobileProject {
17
35
  /**
18
36
  * All React Native versions have app/build.gradle with android section.
19
37
  */
@@ -22,7 +40,7 @@ export class ReactNative extends MobileProject {
22
40
  protected _answers: Answers;
23
41
  protected _sentryCli: SentryCli;
24
42
 
25
- constructor(protected _argv: Args) {
43
+ public constructor(protected _argv: Args) {
26
44
  super(_argv);
27
45
  this._sentryCli = new SentryCli(this._argv);
28
46
  }
@@ -34,43 +52,90 @@ export class ReactNative extends MobileProject {
34
52
  if (!(await this.shouldEmit(answers))) {
35
53
  return {};
36
54
  }
55
+ nl();
56
+
57
+ let userAnswers: Answers = { continue: true };
58
+ const packageManager = getPackageMangerChoice();
59
+
60
+ const hasCompatibleReactNativeVersion = checkPackageVersion(
61
+ this._readAppPackage(),
62
+ REACT_NATIVE_PACKAGE,
63
+ COMPATIBLE_REACT_NATIVE_VERSIONS,
64
+ true,
65
+ );
66
+ if (!hasCompatibleReactNativeVersion && !this._argv.quiet) {
67
+ userAnswers = await prompt({
68
+ message: 'Your version of React Native is not compatible with Sentry\'s React Native SDK. Do you want to continue?',
69
+ name: 'continue',
70
+ default: false,
71
+ type: 'confirm',
72
+ });
73
+ nl();
74
+ }
75
+ if (!userAnswers.continue) {
76
+ throw new Error(
77
+ `Please upgrade to a version that is compatible with ${COMPATIBLE_REACT_NATIVE_VERSIONS}. Or use ${DOCS_MANUAL_STEPS}`,
78
+ );
79
+ }
80
+
81
+ if (packageManager) {
82
+ await packageManager.installPackage(SENTRY_REACT_NATIVE_PACKAGE);
83
+ }
84
+ const hasCompatibleSentryReactNativeVersion = checkPackageVersion(
85
+ this._readAppPackage(),
86
+ SENTRY_REACT_NATIVE_PACKAGE,
87
+ COMPATIBLE_SDK_VERSION,
88
+ true,
89
+ );
90
+ if (!hasCompatibleSentryReactNativeVersion && !this._argv.quiet) {
91
+ userAnswers = await prompt({
92
+ message: `Your version of ${SENTRY_REACT_NATIVE_PACKAGE} is not compatible with this wizard. Do you want to continue?`,
93
+ name: 'continue',
94
+ default: false,
95
+ type: 'confirm',
96
+ });
97
+ nl();
98
+ }
99
+ if (!userAnswers.continue) {
100
+ throw new Error(
101
+ `Please upgrade to a version that is compatible with ${COMPATIBLE_SDK_VERSION}.`,
102
+ );
103
+ }
37
104
 
38
105
  const sentryCliProperties = this._sentryCli.convertAnswersToProperties(
39
106
  answers,
40
107
  );
41
108
 
42
- // eslint-disable-next-line no-async-promise-executor
43
- return new Promise(async (resolve, reject) => {
44
- const promises = this.getPlatforms(answers).map(
45
- async (platform: string) => {
46
- try {
47
- if (platform === 'ios') {
48
- await patchMatchingFile(
49
- 'ios/*.xcodeproj/project.pbxproj',
50
- this._patchXcodeProj.bind(this),
51
- );
52
- dim(`✅ Patched build script in Xcode project.`);
53
- } else {
54
- await patchMatchingFile(
55
- '**/app/build.gradle',
56
- this._patchBuildGradle.bind(this),
57
- );
58
- dim(`✅ Patched build.gradle file.`);
59
- }
60
- await this._patchJsSentryInit(platform, answers);
61
- await this._addSentryProperties(platform, sentryCliProperties);
62
- dim(`✅ Added sentry.properties file to ${platform}`);
63
-
64
- green(`Successfully set up ${platform} for react-native`);
65
- } catch (e) {
66
- red(e);
109
+ const promises = this.getPlatforms(answers).map(
110
+ async (platform: string) => {
111
+ try {
112
+ if (platform === 'ios') {
113
+ await patchMatchingFile(
114
+ 'ios/*.xcodeproj/project.pbxproj',
115
+ this._patchXcodeProj.bind(this),
116
+ );
117
+ green('✓ Patched build script in Xcode project.');
118
+ await this._podInstall();
119
+ green('✓ Pods installed.');
120
+ } else {
121
+ await patchMatchingFile(
122
+ '**/app/build.gradle',
123
+ this._patchBuildGradle.bind(this),
124
+ );
125
+ green('✓ Patched build.gradle file.');
67
126
  }
68
- },
69
- );
70
- Promise.all(promises)
71
- .then(resolve)
72
- .catch(reject);
73
- });
127
+ await this._patchJsSentryInit(platform, answers);
128
+ await this._addSentryProperties(platform, sentryCliProperties);
129
+ green(`✓ Added sentry.properties file to ${platform}`);
130
+ } catch (e) {
131
+ red(e);
132
+ }
133
+ },
134
+ );
135
+
136
+ await Promise.all(promises);
137
+
138
+ return answers;
74
139
  }
75
140
 
76
141
  public async uninstall(_answers: Answers): Promise<Answers> {
@@ -125,6 +190,24 @@ export class ReactNative extends MobileProject {
125
190
  return result;
126
191
  }
127
192
 
193
+ private _readAppPackage(): Record<string, unknown> {
194
+ let appPackage: Record<string, unknown> = {};
195
+
196
+ try {
197
+ appPackage = JSON.parse(
198
+ fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'),
199
+ );
200
+ } catch {
201
+ // We don't need to have this
202
+ }
203
+
204
+ return appPackage;
205
+ }
206
+
207
+ private async _podInstall(): Promise<void> {
208
+ await promisify(exec)('npx --yes pod-install --non-interactive --quiet');
209
+ }
210
+
128
211
  private async _patchJsSentryInit(
129
212
  platform: string,
130
213
  answers: Answers,
@@ -144,10 +227,10 @@ export class ReactNative extends MobileProject {
144
227
  answers,
145
228
  platform,
146
229
  );
147
- dim(`✅ Patched ${jsFileToPatch.join(', ')} file(s).`);
230
+ green(`✓ Patched ${jsFileToPatch.join(', ')} file(s).`);
148
231
  } else {
149
- dim(`🚨 Could not find ${platformGlob} nor ${universalGlob} files.`);
150
- yellow(' Please, visit https://docs.sentry.io/platforms/react-native');
232
+ red(`✗ Could not find ${platformGlob} nor ${universalGlob} files.`);
233
+ red(' Please, visit https://docs.sentry.io/platforms/react-native');
151
234
  }
152
235
  }
153
236
 
@@ -211,9 +294,9 @@ export class ReactNative extends MobileProject {
211
294
  // eslint-disable-next-line prefer-template
212
295
  match +
213
296
  "\n\nimport * as Sentry from '@sentry/react-native';\n\n" +
214
- `Sentry.init({ \n` +
297
+ 'Sentry.init({ \n' +
215
298
  ` dsn: '${dsn}', \n` +
216
- `});\n`,
299
+ '});\n',
217
300
  ),
218
301
  );
219
302
  }
@@ -250,9 +333,7 @@ export class ReactNative extends MobileProject {
250
333
  private _patchExistingXcodeBuildScripts(buildScripts: any): void {
251
334
  for (const script of buildScripts) {
252
335
  if (
253
- !script.shellScript.match(
254
- /\/scripts\/react-native-xcode\.sh/i,
255
- ) ||
336
+ !script.shellScript.match(/\/scripts\/react-native-xcode\.sh/i) ||
256
337
  script.shellScript.match(/sentry-cli\s+react-native\s+xcode/i)
257
338
  ) {
258
339
  continue;
@@ -266,7 +347,7 @@ export class ReactNative extends MobileProject {
266
347
  '$REACT_NATIVE_XCODE',
267
348
  () =>
268
349
  // eslint-disable-next-line no-useless-escape
269
- '\\\"../node_modules/@sentry/cli/bin/sentry-cli react-native xcode $REACT_NATIVE_XCODE\\\"',
350
+ '\\"../node_modules/@sentry/cli/bin/sentry-cli react-native xcode $REACT_NATIVE_XCODE\\"',
270
351
  ) +
271
352
  '\n/bin/sh ../node_modules/@sentry/react-native/scripts/collect-modules.sh\n';
272
353
  script.shellScript = JSON.stringify(code);
@@ -275,7 +356,11 @@ export class ReactNative extends MobileProject {
275
356
 
276
357
  private _addNewXcodeBuildPhaseForSymbols(buildScripts: any, proj: any): void {
277
358
  for (const script of buildScripts) {
278
- if (script.shellScript.match(/sentry-cli\s+(upload-dsym|debug-files upload)/)) {
359
+ if (
360
+ script.shellScript.match(
361
+ /sentry-cli\s+(upload-dsym|debug-files upload)/,
362
+ )
363
+ ) {
279
364
  return;
280
365
  }
281
366
  }
@@ -287,7 +372,7 @@ export class ReactNative extends MobileProject {
287
372
  null,
288
373
  {
289
374
  shellPath: '/bin/sh',
290
- shellScript:`
375
+ shellScript: `
291
376
  export SENTRY_PROPERTIES=sentry.properties
292
377
  [[ $SENTRY_INCLUDE_NATIVE_SOURCES == "true" ]] && INCLUDE_SOURCES_FLAG="--include-sources" || INCLUDE_SOURCES_FLAG=""
293
378
  ../node_modules/@sentry/cli/bin/sentry-cli debug-files upload "$INCLUDE_SOURCES_FLAG" "$DWARF_DSYM_FOLDER_PATH"
@@ -296,7 +381,10 @@ export SENTRY_PROPERTIES=sentry.properties
296
381
  );
297
382
  }
298
383
 
299
- private _patchXcodeProj(contents: string, filename: string): Promise<string> {
384
+ private _patchXcodeProj(
385
+ contents: string,
386
+ filename: string,
387
+ ): Promise<string | undefined> {
300
388
  const proj = xcode.project(filename);
301
389
  return new Promise((resolve, reject) => {
302
390
  proj.parse((err: any) => {
@@ -338,7 +426,7 @@ export SENTRY_PROPERTIES=sentry.properties
338
426
  // continue prompt.
339
427
  const newContents = proj.writeSync();
340
428
  if (newContents === contents) {
341
- resolve();
429
+ resolve(undefined);
342
430
  } else {
343
431
  resolve(newContents);
344
432
  }
@@ -371,7 +459,10 @@ export SENTRY_PROPERTIES=sentry.properties
371
459
  JSON.parse(script.shellScript)
372
460
  // remove sentry properties export
373
461
  .replace(/^export SENTRY_PROPERTIES=sentry.properties\r?\n/m, '')
374
- .replace(/^\/bin\/sh ..\/node_modules\/@sentry\/react-native\/scripts\/collect-modules.sh\r?\n/m, '')
462
+ .replace(
463
+ /^\/bin\/sh ..\/node_modules\/@sentry\/react-native\/scripts\/collect-modules.sh\r?\n/m,
464
+ '',
465
+ )
375
466
  // unwrap react-native-xcode.sh command. In case someone replaced it
376
467
  // entirely with the sentry-cli command we need to put the original
377
468
  // version back in.
@@ -1,17 +1,21 @@
1
1
  jest.mock('../../../Helper/Logging.ts'); // We mock logging to not pollute the output
2
+ jest.mock('child_process');
3
+ import * as child_process from 'child_process';
2
4
  import * as fs from 'fs';
3
- import { Answers } from 'inquirer';
5
+ import type { Answers } from 'inquirer';
4
6
  import * as path from 'path';
5
7
  import * as process from 'process';
6
8
  import * as rimraf from 'rimraf';
7
9
 
8
- import { Args, Integration, Platform } from '../../../Constants';
10
+ import type { Args} from '../../../Constants';
11
+ import { Integration, Platform } from '../../../Constants';
9
12
  import { ReactNative } from '../ReactNative';
10
13
 
11
14
  const testDir = 'rn-test';
12
15
  const iosIndexJs = 'index.ios.js';
13
16
  const appTsx = 'src/App.tsx';
14
17
  const appBuildGradle = 'android/app/build.gradle';
18
+ const yarnLock = 'yarn.lock';
15
19
 
16
20
  const dummyJsContent = 'import React from "react";\n';
17
21
  const dummyAppBuildGradleContent = 'apply plugin: "com.facebook.react"\n\nandroid {\n}\n';
@@ -44,6 +48,17 @@ const mockAndroidAnswers: Answers = {
44
48
  },
45
49
  };
46
50
 
51
+ const originalExec = child_process.exec;
52
+
53
+ const restoreExec = (): void => {
54
+ (child_process as any).exec = originalExec;
55
+ }
56
+
57
+ const mockExec = (): void => {
58
+ (child_process.exec as unknown as jest.Mock)
59
+ .mockImplementation((_command, callback) => callback(null, { stdout: '' }));
60
+ }
61
+
47
62
  describe('ReactNative', () => {
48
63
 
49
64
  const defaultCwd = process.cwd();
@@ -57,9 +72,12 @@ describe('ReactNative', () => {
57
72
  fs.writeFileSync(appTsx, dummyJsContent);
58
73
  fs.mkdirSync(path.dirname(appBuildGradle), { recursive: true });
59
74
  fs.writeFileSync(appBuildGradle, dummyAppBuildGradleContent);
75
+ fs.writeFileSync(yarnLock, '');
76
+ mockExec();
60
77
  });
61
78
 
62
79
  afterEach(() => {
80
+ restoreExec();
63
81
  process.chdir(defaultCwd);
64
82
  rimraf.sync(testDir);
65
83
  });
@@ -81,6 +99,7 @@ describe('ReactNative', () => {
81
99
 
82
100
  test('patches android app build gradle file', async () => {
83
101
  const project = new ReactNative(testArgs as Args);
102
+
84
103
  await project.emit(mockAndroidAnswers);
85
104
 
86
105
  const patchedAppBuildGradle = fs.readFileSync(appBuildGradle, 'utf8');
@@ -89,4 +108,20 @@ describe('ReactNative', () => {
89
108
  'android {\n}\n';
90
109
  expect(patchedAppBuildGradle).toEqual(expectedPatch);
91
110
  });
111
+
112
+ test('does install sentry sdk', async () => {
113
+ const project = new ReactNative(testArgs as Args);
114
+
115
+ await project.emit(mockIosAnswers);
116
+
117
+ expect(child_process.exec).toHaveBeenCalledWith('yarn add @sentry/react-native', expect.anything());
118
+ });
119
+
120
+ test('executes pod install', async () => {
121
+ const project = new ReactNative(testArgs as Args);
122
+
123
+ await project.emit(mockIosAnswers);
124
+
125
+ expect(child_process.exec).toHaveBeenCalledWith('npx --yes pod-install --non-interactive --quiet', expect.anything());
126
+ });
92
127
  });
@@ -1,4 +1,4 @@
1
- import { Answers } from 'inquirer';
1
+ import type { Answers } from 'inquirer';
2
2
  import { URL } from 'url';
3
3
 
4
4
  import { mapIntegrationToPlatform } from '../Constants';
@@ -1,4 +1,5 @@
1
- import { Answers, prompt } from 'inquirer';
1
+ import type { Answers } from 'inquirer';
2
+ import { prompt } from 'inquirer';
2
3
  import * as _ from 'lodash';
3
4
 
4
5
  import { dim } from '../Helper/Logging';
@@ -130,7 +131,7 @@ export class PromptForParameters extends BaseStep {
130
131
  return 'Please copy the slug from the url, it should be all lowercase';
131
132
  }
132
133
  if (input.length === 0) {
133
- return 'Can\'t be empty';
134
+ return "Can't be empty";
134
135
  }
135
136
  return true;
136
137
  }
@@ -1,4 +1,4 @@
1
- import { Answers } from 'inquirer';
1
+ import type { Answers } from 'inquirer';
2
2
 
3
3
  import { green, nl } from '../Helper/Logging';
4
4
  import { BaseStep } from './BaseStep';
@@ -1,10 +1,11 @@
1
- import { Answers, prompt } from 'inquirer';
1
+ import type { Answers } from 'inquirer';
2
+ import { prompt } from 'inquirer';
2
3
  import * as _ from 'lodash';
3
4
 
4
5
  import { BaseStep } from './BaseStep';
5
6
 
6
7
  function sleep(n: number): Promise<void> {
7
- return new Promise(resolve => setTimeout(resolve, n));
8
+ return new Promise((resolve) => setTimeout(resolve, n));
8
9
  }
9
10
 
10
11
  export class SentryProjectSelector extends BaseStep {
@@ -1,4 +1,4 @@
1
- import { Answers } from 'inquirer';
1
+ import type { Answers } from 'inquirer';
2
2
 
3
3
  import { getCurrentIntegration } from '../Helper/Wizard';
4
4
  import { BaseStep } from './BaseStep';
@@ -1,4 +1,4 @@
1
- import { Answers } from 'inquirer';
1
+ import type { Answers } from 'inquirer';
2
2
 
3
3
  import { BottomBar } from '../Helper/BottomBar';
4
4
  import { getCurrentIntegration } from '../Helper/Wizard';
@@ -28,7 +28,7 @@ export class WaitForSentry extends BaseStep {
28
28
  const response = await r2.get(
29
29
  `${baseUrl}api/0/wizard/${answers.hash}/`,
30
30
  ).response;
31
- this.debug(`Polling received data`);
31
+ this.debug('Polling received data');
32
32
  if (response.status !== 200) {
33
33
  throw new Error(`Received status ${response.status}`);
34
34
  }
@@ -36,7 +36,7 @@ export class WaitForSentry extends BaseStep {
36
36
  // Delete the wizard hash since we were able to fetch the data
37
37
  await r2.delete(`${baseUrl}api/0/wizard/${answers.hash}/`);
38
38
  BottomBar.hide();
39
- this.debug(`Polling Success!`);
39
+ this.debug('Polling Success!');
40
40
  resolve({ wizard: data });
41
41
  } catch (e) {
42
42
  this.debug('Polling received:');
@@ -1,4 +1,4 @@
1
- import { Answers } from 'inquirer';
1
+ import type { Answers } from 'inquirer';
2
2
 
3
3
  import { dim, green } from '../Helper/Logging';
4
4
  import { BaseStep } from './BaseStep';
@@ -1,8 +1,31 @@
1
1
  jest.mock('../Helper/Logging'); // We mock logging to not pollute the output
2
+ jest.mock('child_process');
3
+ import * as child_process from 'child_process';
4
+
2
5
  import { Integration, Platform } from '../Constants';
3
6
  import { run } from '../Setup';
4
7
 
8
+ const originalExec = child_process.exec;
9
+
10
+ const restoreExec = (): void => {
11
+ (child_process as any).exec = originalExec;
12
+ };
13
+
14
+ const mockExec = (): void => {
15
+ (child_process.exec as unknown as jest.Mock).mockImplementation(
16
+ (_command, callback) => callback(null, { stdout: '' }),
17
+ );
18
+ };
19
+
5
20
  describe('Wizard', () => {
21
+ beforeEach(() => {
22
+ mockExec();
23
+ });
24
+
25
+ afterEach(() => {
26
+ restoreExec();
27
+ });
28
+
6
29
  describe('React Native', () => {
7
30
  test('run', () => {
8
31
  // eslint-disable-next-line @typescript-eslint/no-floating-promises