@sentry/wizard 3.14.1 → 3.16.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 (120) hide show
  1. package/CHANGELOG.md +19 -4
  2. package/dist/lib/Steps/ChooseIntegration.js +1 -1
  3. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  4. package/dist/lib/Steps/Integrations/ReactNative.d.ts +7 -32
  5. package/dist/lib/Steps/Integrations/ReactNative.js +17 -485
  6. package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
  7. package/dist/package.json +1 -1
  8. package/dist/src/android/android-wizard.js +13 -18
  9. package/dist/src/android/android-wizard.js.map +1 -1
  10. package/dist/src/apple/apple-wizard.js +11 -4
  11. package/dist/src/apple/apple-wizard.js.map +1 -1
  12. package/dist/src/apple/cocoapod.d.ts +1 -0
  13. package/dist/src/apple/cocoapod.js +36 -13
  14. package/dist/src/apple/cocoapod.js.map +1 -1
  15. package/dist/src/nextjs/nextjs-wizard.js +1 -1
  16. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  17. package/dist/src/react-native/glob.d.ts +3 -0
  18. package/dist/src/react-native/glob.js +18 -0
  19. package/dist/src/react-native/glob.js.map +1 -0
  20. package/dist/src/react-native/gradle.d.ts +4 -0
  21. package/dist/src/react-native/gradle.js +49 -0
  22. package/dist/src/react-native/gradle.js.map +1 -0
  23. package/dist/src/react-native/javascript.d.ts +8 -0
  24. package/dist/src/react-native/javascript.js +25 -0
  25. package/dist/src/react-native/javascript.js.map +1 -0
  26. package/dist/src/react-native/options.d.ts +4 -0
  27. package/dist/src/react-native/options.js +3 -0
  28. package/dist/src/react-native/options.js.map +1 -0
  29. package/dist/src/react-native/react-native-wizard.d.ts +9 -0
  30. package/dist/src/react-native/react-native-wizard.js +356 -0
  31. package/dist/src/react-native/react-native-wizard.js.map +1 -0
  32. package/dist/src/react-native/uninstall.d.ts +2 -0
  33. package/dist/src/react-native/uninstall.js +130 -0
  34. package/dist/src/react-native/uninstall.js.map +1 -0
  35. package/dist/src/react-native/xcode.d.ts +18 -0
  36. package/dist/src/react-native/xcode.js +170 -0
  37. package/dist/src/react-native/xcode.js.map +1 -0
  38. package/dist/src/remix/codemods/handle-error.js +28 -0
  39. package/dist/src/remix/codemods/handle-error.js.map +1 -1
  40. package/dist/src/remix/codemods/root-common.d.ts +2 -0
  41. package/dist/src/remix/codemods/root-common.js +70 -0
  42. package/dist/src/remix/codemods/root-common.js.map +1 -0
  43. package/dist/src/remix/codemods/root-v1.js +5 -36
  44. package/dist/src/remix/codemods/root-v1.js.map +1 -1
  45. package/dist/src/remix/codemods/root-v2.js +53 -4
  46. package/dist/src/remix/codemods/root-v2.js.map +1 -1
  47. package/dist/src/remix/remix-wizard.js +8 -5
  48. package/dist/src/remix/remix-wizard.js.map +1 -1
  49. package/dist/src/remix/sdk-setup.d.ts +1 -0
  50. package/dist/src/remix/sdk-setup.js +10 -6
  51. package/dist/src/remix/sdk-setup.js.map +1 -1
  52. package/dist/src/remix/templates.d.ts +1 -1
  53. package/dist/src/remix/templates.js +1 -1
  54. package/dist/src/remix/templates.js.map +1 -1
  55. package/dist/src/remix/utils.d.ts +2 -0
  56. package/dist/src/remix/utils.js +6 -1
  57. package/dist/src/remix/utils.js.map +1 -1
  58. package/dist/src/sourcemaps/tools/nextjs.js +3 -3
  59. package/dist/src/sourcemaps/tools/nextjs.js.map +1 -1
  60. package/dist/src/sourcemaps/tools/sentry-cli.js +1 -1
  61. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  62. package/dist/src/sveltekit/sveltekit-wizard.js +1 -1
  63. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  64. package/dist/src/utils/clack-utils.d.ts +19 -3
  65. package/dist/src/utils/clack-utils.js +141 -39
  66. package/dist/src/utils/clack-utils.js.map +1 -1
  67. package/dist/src/utils/semver.d.ts +5 -0
  68. package/dist/src/utils/semver.js +27 -0
  69. package/dist/src/utils/semver.js.map +1 -0
  70. package/dist/src/utils/sentrycli-utils.js +4 -1
  71. package/dist/src/utils/sentrycli-utils.js.map +1 -1
  72. package/dist/src/utils/types.d.ts +3 -0
  73. package/dist/src/utils/types.js.map +1 -1
  74. package/dist/test/react-native/gradle.test.js +57 -0
  75. package/dist/test/react-native/gradle.test.js.map +1 -0
  76. package/dist/test/react-native/javascript.test.js +47 -0
  77. package/dist/test/react-native/javascript.test.js.map +1 -0
  78. package/dist/test/react-native/xcode.test.d.ts +1 -0
  79. package/dist/test/react-native/xcode.test.js +144 -0
  80. package/dist/test/react-native/xcode.test.js.map +1 -0
  81. package/lib/Steps/ChooseIntegration.ts +1 -1
  82. package/lib/Steps/Integrations/ReactNative.ts +17 -573
  83. package/package.json +1 -1
  84. package/src/android/android-wizard.ts +3 -18
  85. package/src/apple/apple-wizard.ts +12 -3
  86. package/src/apple/cocoapod.ts +20 -9
  87. package/src/nextjs/nextjs-wizard.ts +1 -1
  88. package/src/react-native/glob.ts +13 -0
  89. package/src/react-native/gradle.ts +26 -0
  90. package/src/react-native/javascript.ts +33 -0
  91. package/src/react-native/options.ts +5 -0
  92. package/src/react-native/react-native-wizard.ts +369 -0
  93. package/src/react-native/uninstall.ts +107 -0
  94. package/src/react-native/xcode.ts +228 -0
  95. package/src/remix/codemods/handle-error.ts +30 -0
  96. package/src/remix/codemods/root-common.ts +63 -0
  97. package/src/remix/codemods/root-v1.ts +3 -53
  98. package/src/remix/codemods/root-v2.ts +71 -2
  99. package/src/remix/remix-wizard.ts +9 -6
  100. package/src/remix/sdk-setup.ts +14 -6
  101. package/src/remix/templates.ts +2 -6
  102. package/src/remix/utils.ts +5 -0
  103. package/src/sourcemaps/tools/nextjs.ts +6 -6
  104. package/src/sourcemaps/tools/sentry-cli.ts +1 -1
  105. package/src/sveltekit/sveltekit-wizard.ts +1 -1
  106. package/src/utils/clack-utils.ts +229 -74
  107. package/src/utils/semver.ts +33 -0
  108. package/src/utils/sentrycli-utils.ts +3 -1
  109. package/src/utils/types.ts +3 -0
  110. package/test/react-native/gradle.test.ts +310 -0
  111. package/test/react-native/javascript.test.ts +131 -0
  112. package/test/react-native/xcode.test.ts +238 -0
  113. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js +0 -198
  114. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js.map +0 -1
  115. package/dist/lib/__tests__/Setup.js +0 -57
  116. package/dist/lib/__tests__/Setup.js.map +0 -1
  117. package/lib/Steps/Integrations/__tests__/ReactNative.ts +0 -136
  118. package/lib/__tests__/Setup.ts +0 -42
  119. /package/dist/{lib/Steps/Integrations/__tests__/ReactNative.d.ts → test/react-native/gradle.test.d.ts} +0 -0
  120. /package/dist/{lib/__tests__/Setup.d.ts → test/react-native/javascript.test.d.ts} +0 -0
@@ -109,6 +109,7 @@ async function runAppleWizardWithTelementry(
109
109
 
110
110
  if (availableTargets.length == 0) {
111
111
  clack.log.error(`No suttable target found in ${xcodeProjFile}`);
112
+ Sentry.setTag('No-Target', true);
112
113
  await abort();
113
114
  return;
114
115
  }
@@ -131,6 +132,7 @@ async function runAppleWizardWithTelementry(
131
132
  );
132
133
 
133
134
  let hasCocoa = cocoapod.usesCocoaPod(projectDir);
135
+ Sentry.setTag('cocoapod-exists', hasCocoa);
134
136
 
135
137
  if (hasCocoa) {
136
138
  const pm = (
@@ -147,6 +149,7 @@ async function runAppleWizardWithTelementry(
147
149
  const podAdded = await traceStep('Add CocoaPods reference', () =>
148
150
  cocoapod.addCocoaPods(projectDir),
149
151
  );
152
+ Sentry.setTag('cocoapod-added', podAdded);
150
153
  if (!podAdded) {
151
154
  clack.log.warn(
152
155
  "Could not add Sentry pod to your Podfile. You'll have to add it manually.\nPlease follow the instructions at https://docs.sentry.io/platforms/apple/guides/ios/#install",
@@ -155,11 +158,11 @@ async function runAppleWizardWithTelementry(
155
158
  }
156
159
  }
157
160
 
161
+ Sentry.setTag('package-manager', hasCocoa ? 'cocoapods' : 'SPM');
158
162
  traceStep('Update Xcode project', () => {
159
163
  xcProject.updateXcodeProject(project, target, apiKey, !hasCocoa, true);
160
164
  });
161
165
 
162
- Sentry.setTag('package-manager', hasCocoa ? 'cocoapods' : 'SPM');
163
166
  const codeAdded = traceStep('Add code snippet', () => {
164
167
  const files = xcProject.filesForTarget(target);
165
168
  if (files === undefined || files.length == 0) return false;
@@ -170,18 +173,23 @@ async function runAppleWizardWithTelementry(
170
173
  project.keys[0].dsn.public,
171
174
  );
172
175
  });
176
+
177
+ Sentry.setTag('Snippet-Added', codeAdded);
178
+
173
179
  if (!codeAdded) {
174
180
  clack.log.warn(
175
181
  'Added the Sentry dependency to your project but could not add the Sentry code snippet. Please add the code snipped manually by following the docs: https://docs.sentry.io/platforms/apple/guides/ios/#configure',
176
182
  );
177
- return;
178
183
  }
179
184
 
180
- if (fastlane.fastFile(projectDir)) {
185
+ const hasFastlane = fastlane.fastFile(projectDir);
186
+ Sentry.setTag('fastlane-exists', hasFastlane);
187
+ if (hasFastlane) {
181
188
  const addLane = await clack.confirm({
182
189
  message:
183
190
  'Found a Fastfile in your project. Do you want to configure a lane to upload debug symbols to Sentry?',
184
191
  });
192
+ Sentry.setTag('fastlane-desired', addLane);
185
193
  if (addLane) {
186
194
  const added = await traceStep('Configure fastlane', () =>
187
195
  fastlane.addSentryToFastlane(
@@ -190,6 +198,7 @@ async function runAppleWizardWithTelementry(
190
198
  project.slug,
191
199
  ),
192
200
  );
201
+ Sentry.setTag('fastlane-added', added);
193
202
  if (added) {
194
203
  clack.log.step(
195
204
  'A new step was added to your fastlane file. Now and you build your project with fastlane, debug symbols and source context will be uploaded to Sentry.',
@@ -4,6 +4,7 @@ import * as bash from '../utils/bash';
4
4
  import * as Sentry from '@sentry/node';
5
5
  // @ts-ignore - clack is ESM and TS complains about that. It works though
6
6
  import * as clack from '@clack/prompts';
7
+ import chalk from 'chalk';
7
8
 
8
9
  export function usesCocoaPod(projPath: string): boolean {
9
10
  return fs.existsSync(path.join(projPath, 'Podfile'));
@@ -40,21 +41,31 @@ export async function addCocoaPods(projPath: string): Promise<boolean> {
40
41
  podContent.slice(insertIndex);
41
42
  fs.writeFileSync(podfile, newFileContent, 'utf8');
42
43
 
43
- const loginSpinner = clack.spinner();
44
-
45
44
  clack.log.step('Sentry pod added to the project podFile.');
46
- loginSpinner.start("Running 'pod install'. This may take a few minutes...");
45
+
46
+ await podInstall();
47
+
48
+ return true;
49
+ }
50
+
51
+ export async function podInstall(dir = '.') {
52
+ const installSpinner = clack.spinner();
53
+ installSpinner.start("Running 'pod install'. This may take a few minutes...");
47
54
 
48
55
  try {
49
- await bash.execute('pod install --silent');
50
- loginSpinner.stop('Running "pod install"');
56
+ await bash.execute(`cd ${dir} && pod repo update`);
57
+ await bash.execute(`cd ${dir} && pod install --silent`);
58
+ installSpinner.stop('Pods installed.');
51
59
  } catch (e) {
52
- loginSpinner.stop('Running "pod install"');
60
+ installSpinner.stop('Failed to install pods.');
53
61
  clack.log.error(
54
- 'Failed to run "pod install". You can run it manually for more details.',
62
+ `${chalk.red(
63
+ 'Encountered the following error during pods installation:',
64
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
65
+ )}\n\n${e}\n\n${chalk.dim(
66
+ 'If you think this issue is caused by the Sentry wizard, let us know here:\nhttps://github.com/getsentry/sentry-wizard/issues',
67
+ )}`,
55
68
  );
56
69
  Sentry.captureException('Sentry pod install failed.');
57
70
  }
58
-
59
- return true;
60
71
  }
@@ -87,7 +87,7 @@ export async function runNextjsWizardWithTelemetry(
87
87
  createExamplePage(selfHosted, selectedProject, sentryUrl),
88
88
  );
89
89
 
90
- await addSentryCliConfig(authToken);
90
+ await addSentryCliConfig({ authToken });
91
91
 
92
92
  const mightBeUsingVercel = fs.existsSync(
93
93
  path.join(process.cwd(), 'vercel.json'),
@@ -0,0 +1,13 @@
1
+ import glob from 'glob';
2
+
3
+ export const XCODE_PROJECT = 'ios/*.xcodeproj/project.pbxproj';
4
+ export const APP_BUILD_GRADLE = '**/app/build.gradle';
5
+
6
+ const IGNORE_PATTERNS = ['node_modules/**', 'ios/Pods/**', '**/Pods/**'];
7
+ export function getFirstMatchedPath(pattern: string): string | undefined {
8
+ const matches = glob.sync(pattern, {
9
+ ignore: IGNORE_PATTERNS,
10
+ });
11
+
12
+ return matches[0];
13
+ }
@@ -0,0 +1,26 @@
1
+ import * as fs from 'fs';
2
+
3
+ const applyFrom = `apply from: new File(["node", "--print", "require.resolve('@sentry/react-native/package.json')"].execute().text.trim(), "../sentry.gradle")`;
4
+
5
+ export function doesAppBuildGradleIncludeRNSentryGradlePlugin(
6
+ content: string,
7
+ ): boolean {
8
+ return content.includes('sentry.gradle');
9
+ }
10
+
11
+ export function addRNSentryGradlePlugin(content: string): string {
12
+ return content.replace(/^android {/m, (match) => `${applyFrom}\n${match}`);
13
+ }
14
+
15
+ export function removeRNSentryGradlePlugin(content: string): string {
16
+ return content.replace(/^\s*apply from:.*sentry\.gradle.*;?\s*?\r?\n/m, '');
17
+ }
18
+
19
+ export function writeAppBuildGradle(path: string, newContent: string): void {
20
+ const currentContent = fs.readFileSync(path, 'utf-8');
21
+ if (newContent === currentContent) {
22
+ return;
23
+ }
24
+
25
+ fs.writeFileSync(path, newContent, 'utf-8');
26
+ }
@@ -0,0 +1,33 @@
1
+ import { makeCodeSnippet } from '../utils/clack-utils';
2
+
3
+ export function addSentryInitWithSdkImport(
4
+ js: string,
5
+ { dsn }: { dsn: string },
6
+ ): string {
7
+ return js.replace(
8
+ /^([^]*)(import\s+[^;]*?;$)/m,
9
+ (match: string) => `${match}
10
+ ${getSentryInitPlainTextSnippet(dsn)}`,
11
+ );
12
+ }
13
+
14
+ export function doesJsCodeIncludeSdkSentryImport(
15
+ js: string,
16
+ { sdkPackageName }: { sdkPackageName: string },
17
+ ): boolean {
18
+ return !!js.match(sdkPackageName);
19
+ }
20
+
21
+ export function getSentryInitColoredCodeSnippet(dsn: string) {
22
+ return makeCodeSnippet(true, (_unchanged, plus, _minus) => {
23
+ return plus(getSentryInitPlainTextSnippet(dsn));
24
+ });
25
+ }
26
+
27
+ export function getSentryInitPlainTextSnippet(dsn: string) {
28
+ return `import * as Sentry from '@sentry/react-native';
29
+
30
+ Sentry.init({
31
+ dsn: '${dsn}',
32
+ });`;
33
+ }
@@ -0,0 +1,5 @@
1
+ import { WizardOptions } from '../utils/types';
2
+
3
+ export interface ReactNativeWizardOptions extends WizardOptions {
4
+ uninstall: boolean;
5
+ }
@@ -0,0 +1,369 @@
1
+ /* eslint-disable max-lines */
2
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
3
+ import clack from '@clack/prompts';
4
+ import chalk from 'chalk';
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import * as process from 'process';
8
+ import {
9
+ CliSetupConfigContent,
10
+ addSentryCliConfig,
11
+ confirmContinueIfNoOrDirtyGitRepo,
12
+ confirmContinueIfPackageVersionNotSupported,
13
+ ensurePackageIsInstalled,
14
+ getOrAskForProjectData,
15
+ getPackageDotJson,
16
+ installPackage,
17
+ printWelcome,
18
+ propertiesCliSetupConfig,
19
+ showCopyPasteInstructions,
20
+ } from '../utils/clack-utils';
21
+ import { getPackageVersion, hasPackageInstalled } from '../utils/package-json';
22
+ import { podInstall } from '../apple/cocoapod';
23
+ import { platform } from 'os';
24
+ import {
25
+ getValidExistingBuildPhases,
26
+ findBundlePhase,
27
+ patchBundlePhase,
28
+ findDebugFilesUploadPhase,
29
+ addDebugFilesUploadPhase,
30
+ writeXcodeProject,
31
+ } from './xcode';
32
+ import {
33
+ doesAppBuildGradleIncludeRNSentryGradlePlugin,
34
+ addRNSentryGradlePlugin,
35
+ writeAppBuildGradle,
36
+ } from './gradle';
37
+ import { runReactNativeUninstall } from './uninstall';
38
+ import { APP_BUILD_GRADLE, XCODE_PROJECT, getFirstMatchedPath } from './glob';
39
+ import { ReactNativeWizardOptions } from './options';
40
+ import { SentryProjectData } from '../utils/types';
41
+ import {
42
+ addSentryInitWithSdkImport,
43
+ doesJsCodeIncludeSdkSentryImport,
44
+ getSentryInitColoredCodeSnippet,
45
+ } from './javascript';
46
+ import { traceStep, withTelemetry } from '../telemetry';
47
+ import * as Sentry from '@sentry/node';
48
+
49
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
50
+ const xcode = require('xcode');
51
+
52
+ export const RN_SDK_PACKAGE = '@sentry/react-native';
53
+
54
+ export const RN_PACKAGE = 'react-native';
55
+ export const RN_HUMAN_NAME = 'React Native';
56
+
57
+ export const SUPPORTED_RN_RANGE = '>=0.69.0';
58
+
59
+ export type RNCliSetupConfigContent = Pick<
60
+ Required<CliSetupConfigContent>,
61
+ 'authToken' | 'org' | 'project' | 'url'
62
+ >;
63
+
64
+ export async function runReactNativeWizard(
65
+ params: ReactNativeWizardOptions,
66
+ ): Promise<void> {
67
+ return withTelemetry(
68
+ {
69
+ enabled: params.telemetryEnabled,
70
+ integration: 'react-native',
71
+ },
72
+ () => runReactNativeWizardWithTelemetry(params),
73
+ );
74
+ }
75
+
76
+ export async function runReactNativeWizardWithTelemetry(
77
+ options: ReactNativeWizardOptions,
78
+ ): Promise<void> {
79
+ if (options.uninstall) {
80
+ Sentry.setTag('uninstall', true);
81
+ return runReactNativeUninstall(options);
82
+ }
83
+
84
+ printWelcome({
85
+ wizardName: 'Sentry React Native Wizard',
86
+ promoCode: options.promoCode,
87
+ telemetryEnabled: options.telemetryEnabled,
88
+ });
89
+
90
+ await confirmContinueIfNoOrDirtyGitRepo();
91
+
92
+ const packageJson = await getPackageDotJson();
93
+
94
+ await ensurePackageIsInstalled(packageJson, RN_PACKAGE, RN_HUMAN_NAME);
95
+
96
+ const rnVersion = getPackageVersion(RN_PACKAGE, packageJson);
97
+ if (rnVersion) {
98
+ await confirmContinueIfPackageVersionNotSupported({
99
+ packageName: RN_HUMAN_NAME,
100
+ packageVersion: rnVersion,
101
+ packageId: RN_PACKAGE,
102
+ acceptableVersions: SUPPORTED_RN_RANGE,
103
+ });
104
+ }
105
+
106
+ const { selectedProject, authToken, sentryUrl } =
107
+ await getOrAskForProjectData(options, 'react-native');
108
+ const orgSlug = selectedProject.organization.slug;
109
+ const projectSlug = selectedProject.slug;
110
+ const cliConfig: RNCliSetupConfigContent = {
111
+ authToken,
112
+ org: orgSlug,
113
+ project: projectSlug,
114
+ url: sentryUrl,
115
+ };
116
+
117
+ await installPackage({
118
+ packageName: RN_SDK_PACKAGE,
119
+ alreadyInstalled: hasPackageInstalled(RN_SDK_PACKAGE, packageJson),
120
+ });
121
+
122
+ await traceStep('patch-js', () =>
123
+ addSentryInit({ dsn: selectedProject.keys[0].dsn.public }),
124
+ );
125
+
126
+ if (fs.existsSync('ios')) {
127
+ Sentry.setTag('patch-ios', true);
128
+ await traceStep('patch-xcode-files', () => patchXcodeFiles(cliConfig));
129
+ }
130
+
131
+ if (fs.existsSync('android')) {
132
+ Sentry.setTag('patch-android', true);
133
+ await traceStep('patch-android-files', () => patchAndroidFiles(cliConfig));
134
+ }
135
+
136
+ const confirmedFirstException = await confirmFirstSentryException(
137
+ selectedProject,
138
+ );
139
+ Sentry.setTag('user-confirmed-first-error', confirmedFirstException);
140
+
141
+ if (confirmedFirstException) {
142
+ clack.outro(
143
+ `${chalk.green('Everything is set up!')}
144
+
145
+ ${chalk.dim(
146
+ 'If you encounter any issues, let us know here: https://github.com/getsentry/sentry-react-native/issues',
147
+ )}`,
148
+ );
149
+ } else {
150
+ clack.outro(
151
+ `${chalk.dim(
152
+ 'Let us know here: https://github.com/getsentry/sentry-react-native/issues',
153
+ )}`,
154
+ );
155
+ }
156
+ }
157
+
158
+ async function addSentryInit({ dsn }: { dsn: string }) {
159
+ const prefixGlob = '{.,./src}';
160
+ const suffixGlob = '@(j|t|cj|mj)s?(x)';
161
+ const universalGlob = `App.${suffixGlob}`;
162
+ const jsFileGlob = `${prefixGlob}/+(${universalGlob})`;
163
+ const jsPath = traceStep('find-app-js-file', () =>
164
+ getFirstMatchedPath(jsFileGlob),
165
+ );
166
+ Sentry.setTag('app-js-file-status', jsPath ? 'found' : 'not-found');
167
+ if (!jsPath) {
168
+ clack.log.warn(
169
+ `Could not find main App file using ${chalk.cyan(jsFileGlob)}.`,
170
+ );
171
+ await showCopyPasteInstructions(
172
+ 'App.js',
173
+ getSentryInitColoredCodeSnippet(dsn),
174
+ 'This ensures the Sentry SDK is ready to capture errors.',
175
+ );
176
+ return;
177
+ }
178
+ const jsRelativePath = path.relative(process.cwd(), jsPath);
179
+
180
+ const js = fs.readFileSync(jsPath, 'utf-8');
181
+ const includesSentry = doesJsCodeIncludeSdkSentryImport(js, {
182
+ sdkPackageName: RN_SDK_PACKAGE,
183
+ });
184
+ if (includesSentry) {
185
+ Sentry.setTag('app-js-file-status', 'already-includes-sentry');
186
+ clack.log.warn(
187
+ `${chalk.cyan(
188
+ jsRelativePath,
189
+ )} already includes Sentry. We wont't add it again.`,
190
+ );
191
+ return;
192
+ }
193
+
194
+ traceStep('add-sentry-init', () => {
195
+ const newContent = addSentryInitWithSdkImport(js, { dsn });
196
+
197
+ clack.log.success(
198
+ `Added ${chalk.cyan('Sentry.init')} to ${chalk.cyan(jsRelativePath)}.`,
199
+ );
200
+
201
+ fs.writeFileSync(jsPath, newContent, 'utf-8');
202
+ });
203
+
204
+ Sentry.setTag('app-js-file-status', 'added-sentry-init');
205
+ clack.log.success(
206
+ chalk.green(`${chalk.cyan(jsRelativePath)} changes saved.`),
207
+ );
208
+ }
209
+
210
+ async function confirmFirstSentryException(project: SentryProjectData) {
211
+ const projectsIssuesUrl = `${project.organization.links.organizationUrl}/issues/?project=${project.id}`;
212
+
213
+ clack.log
214
+ .step(`To make sure everything is set up correctly, put the following code snippet into your application.
215
+ The snippet will create a button that, when tapped, sends a test event to Sentry.
216
+
217
+ After that check your project issues:
218
+
219
+ ${chalk.cyan(projectsIssuesUrl)}`);
220
+
221
+ // We want the code snippet to be easily copy-pasteable, without any clack artifacts
222
+ // eslint-disable-next-line no-console
223
+ console.log(
224
+ chalk.greenBright(`
225
+ <Button title='Try!' onPress={ () => { Sentry.captureException(new Error('First error')) }}/>
226
+ `),
227
+ );
228
+
229
+ const firstErrorConfirmed = clack.confirm({
230
+ message: `Have you successfully sent a test event?`,
231
+ });
232
+
233
+ return firstErrorConfirmed;
234
+ }
235
+
236
+ async function patchXcodeFiles(config: RNCliSetupConfigContent) {
237
+ await addSentryCliConfig(config, {
238
+ ...propertiesCliSetupConfig,
239
+ name: 'source maps and iOS debug files',
240
+ filename: 'ios/sentry.properties',
241
+ gitignore: false,
242
+ });
243
+
244
+ if (platform() === 'darwin') {
245
+ await traceStep('pod-install', () => podInstall('ios'));
246
+ Sentry.setTag('pods-installed', true);
247
+ }
248
+
249
+ const xcodeProjectPath = traceStep('find-xcode-project', () =>
250
+ getFirstMatchedPath(XCODE_PROJECT),
251
+ );
252
+ Sentry.setTag(
253
+ 'xcode-project-status',
254
+ xcodeProjectPath ? 'found' : 'not-found',
255
+ );
256
+ if (!xcodeProjectPath) {
257
+ clack.log.warn(
258
+ `Could not find Xcode project file using ${chalk.cyan(XCODE_PROJECT)}.`,
259
+ );
260
+ return;
261
+ }
262
+
263
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
264
+ const [xcodeProject, buildPhasesMap] = traceStep(
265
+ 'parse-xcode-project',
266
+ () => {
267
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
268
+ const project = xcode.project(xcodeProjectPath);
269
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
270
+ project.parseSync();
271
+
272
+ const map = getValidExistingBuildPhases(project);
273
+ return [project, map];
274
+ },
275
+ );
276
+ Sentry.setTag('xcode-project-status', 'parsed');
277
+
278
+ traceStep('patch-bundle-phase', () => {
279
+ const bundlePhase = findBundlePhase(buildPhasesMap);
280
+ Sentry.setTag(
281
+ 'xcode-bundle-phase-status',
282
+ bundlePhase ? 'found' : 'not-found',
283
+ );
284
+ patchBundlePhase(bundlePhase);
285
+ Sentry.setTag('xcode-bundle-phase-status', 'patched');
286
+ });
287
+
288
+ traceStep('add-debug-files-upload-phase', () => {
289
+ const debugFilesUploadPhaseExists =
290
+ !!findDebugFilesUploadPhase(buildPhasesMap);
291
+ Sentry.setTag(
292
+ 'xcode-debug-files-upload-phase-status',
293
+ debugFilesUploadPhaseExists ? 'already-exists' : undefined,
294
+ );
295
+ addDebugFilesUploadPhase(xcodeProject, { debugFilesUploadPhaseExists });
296
+ Sentry.setTag('xcode-debug-files-upload-phase-status', 'added');
297
+ });
298
+
299
+ traceStep('write-xcode-project', () => {
300
+ writeXcodeProject(xcodeProjectPath, xcodeProject);
301
+ });
302
+ Sentry.setTag('xcode-project-status', 'patched');
303
+ }
304
+
305
+ async function patchAndroidFiles(config: RNCliSetupConfigContent) {
306
+ await addSentryCliConfig(config, {
307
+ ...propertiesCliSetupConfig,
308
+ name: 'source maps and iOS debug files',
309
+ filename: 'android/sentry.properties',
310
+ gitignore: false,
311
+ });
312
+
313
+ const appBuildGradlePath = traceStep('find-app-build-gradle', () =>
314
+ getFirstMatchedPath(APP_BUILD_GRADLE),
315
+ );
316
+ Sentry.setTag(
317
+ 'app-build-gradle-status',
318
+ appBuildGradlePath ? 'found' : 'not-found',
319
+ );
320
+ if (!appBuildGradlePath) {
321
+ clack.log.warn(
322
+ `Could not find Android ${chalk.cyan(
323
+ 'app/build.gradle',
324
+ )} file using ${chalk.cyan(APP_BUILD_GRADLE)}.`,
325
+ );
326
+ return;
327
+ }
328
+
329
+ const appBuildGradle = traceStep('read-app-build-gradle', () =>
330
+ fs.readFileSync(appBuildGradlePath, 'utf-8'),
331
+ );
332
+ const includesSentry =
333
+ doesAppBuildGradleIncludeRNSentryGradlePlugin(appBuildGradle);
334
+ if (includesSentry) {
335
+ Sentry.setTag('app-build-gradle-status', 'already-includes-sentry');
336
+ clack.log.warn(
337
+ `Android ${chalk.cyan('app/build.gradle')} file already includes Sentry.`,
338
+ );
339
+ return;
340
+ }
341
+
342
+ const patchedAppBuildGradle = traceStep('add-rn-sentry-gradle-plugin', () =>
343
+ addRNSentryGradlePlugin(appBuildGradle),
344
+ );
345
+ if (!doesAppBuildGradleIncludeRNSentryGradlePlugin(patchedAppBuildGradle)) {
346
+ Sentry.setTag(
347
+ 'app-build-gradle-status',
348
+ 'failed-to-add-rn-sentry-gradle-plugin',
349
+ );
350
+ clack.log.warn(
351
+ `Could not add Sentry RN Gradle Plugin to ${chalk.cyan(
352
+ 'app/build.gradle',
353
+ )}.`,
354
+ );
355
+ return;
356
+ }
357
+
358
+ Sentry.setTag('app-build-gradle-status', 'added-rn-sentry-gradle-plugin');
359
+ clack.log.success(
360
+ `Added Sentry RN Gradle Plugin to ${chalk.bold('app/build.gradle')}.`,
361
+ );
362
+
363
+ traceStep('write-app-build-gradle', () =>
364
+ writeAppBuildGradle(appBuildGradlePath, patchedAppBuildGradle),
365
+ );
366
+ clack.log.success(
367
+ chalk.green(`Android ${chalk.cyan('app/build.gradle')} saved.`),
368
+ );
369
+ }
@@ -0,0 +1,107 @@
1
+ import * as fs from 'fs';
2
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
3
+ import clack from '@clack/prompts';
4
+ import chalk from 'chalk';
5
+
6
+ import {
7
+ confirmContinueIfNoOrDirtyGitRepo,
8
+ printWelcome,
9
+ } from '../utils/clack-utils';
10
+ import {
11
+ findBundlePhase,
12
+ getValidExistingBuildPhases,
13
+ unPatchBundlePhase,
14
+ unPatchDebugFilesUploadPhase,
15
+ writeXcodeProject,
16
+ } from './xcode';
17
+ import { APP_BUILD_GRADLE, XCODE_PROJECT, getFirstMatchedPath } from './glob';
18
+ import {
19
+ doesAppBuildGradleIncludeRNSentryGradlePlugin,
20
+ removeRNSentryGradlePlugin,
21
+ writeAppBuildGradle,
22
+ } from './gradle';
23
+ import { ReactNativeWizardOptions } from './options';
24
+
25
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
26
+ const xcode = require('xcode');
27
+
28
+ export async function runReactNativeUninstall(
29
+ options: ReactNativeWizardOptions,
30
+ ): Promise<void> {
31
+ printWelcome({
32
+ wizardName: 'Sentry React Native Uninstall Wizard',
33
+ message: 'This wizard will remove Sentry from your React Native project.',
34
+ telemetryEnabled: options.telemetryEnabled,
35
+ });
36
+
37
+ await confirmContinueIfNoOrDirtyGitRepo();
38
+
39
+ unPatchXcodeFiles();
40
+
41
+ unPatchAndroidFiles();
42
+
43
+ clack.note(
44
+ `To make sure your project builds after removing Sentry please run:
45
+
46
+ 1. ${chalk.bold('yarn remove @sentry/react-native')}
47
+ 2. ${chalk.bold('cd ios && pod install')}
48
+ 3. Remove all occurrences of ${chalk.bold(
49
+ '@sentry/react-native',
50
+ )} from your application code.`,
51
+ );
52
+
53
+ clack.outro(
54
+ `${chalk.green('Uninstall is done!')}
55
+
56
+ ${chalk.dim(
57
+ 'If you encounter any issues, let us know here: https://github.com/getsentry/sentry-react-native/issues',
58
+ )}`,
59
+ );
60
+ }
61
+
62
+ function unPatchXcodeFiles() {
63
+ const xcodeProjectPath = getFirstMatchedPath(XCODE_PROJECT);
64
+ if (!xcodeProjectPath) {
65
+ clack.log.warn(
66
+ `Could not find Xcode project file using ${chalk.bold(XCODE_PROJECT)}.`,
67
+ );
68
+ return;
69
+ }
70
+
71
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
72
+ const xcodeProject = xcode.project(xcodeProjectPath);
73
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
74
+ xcodeProject.parseSync();
75
+ const buildPhases = getValidExistingBuildPhases(xcodeProject);
76
+
77
+ const bundlePhase = findBundlePhase(buildPhases);
78
+ unPatchBundlePhase(bundlePhase);
79
+
80
+ unPatchDebugFilesUploadPhase(xcodeProject);
81
+
82
+ writeXcodeProject(xcodeProjectPath, xcodeProject);
83
+ }
84
+
85
+ function unPatchAndroidFiles() {
86
+ const appBuildGradlePath = getFirstMatchedPath(APP_BUILD_GRADLE);
87
+ if (!appBuildGradlePath) {
88
+ clack.log.warn(
89
+ `Could not find Android app/build.gradle file using ${chalk.bold(
90
+ APP_BUILD_GRADLE,
91
+ )}.`,
92
+ );
93
+ return;
94
+ }
95
+
96
+ const appBuildGradle = fs.readFileSync(appBuildGradlePath, 'utf-8');
97
+ const includesSentry =
98
+ doesAppBuildGradleIncludeRNSentryGradlePlugin(appBuildGradle);
99
+ if (!includesSentry) {
100
+ clack.log.warn(`Sentry not found in Android app/build.gradle.`);
101
+ return;
102
+ }
103
+
104
+ const patchedAppBuildGradle = removeRNSentryGradlePlugin(appBuildGradle);
105
+
106
+ writeAppBuildGradle(appBuildGradlePath, patchedAppBuildGradle);
107
+ }