@sentry/wizard 3.15.0 → 3.16.1

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 (98) hide show
  1. package/CHANGELOG.md +11 -0
  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/Cordova.js +7 -0
  5. package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
  6. package/dist/lib/Steps/Integrations/ReactNative.d.ts +7 -32
  7. package/dist/lib/Steps/Integrations/ReactNative.js +17 -485
  8. package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
  9. package/dist/package.json +1 -1
  10. package/dist/src/android/android-wizard.js +13 -18
  11. package/dist/src/android/android-wizard.js.map +1 -1
  12. package/dist/src/apple/apple-wizard.js +11 -4
  13. package/dist/src/apple/apple-wizard.js.map +1 -1
  14. package/dist/src/apple/cocoapod.d.ts +1 -0
  15. package/dist/src/apple/cocoapod.js +36 -13
  16. package/dist/src/apple/cocoapod.js.map +1 -1
  17. package/dist/src/nextjs/nextjs-wizard.js +1 -1
  18. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  19. package/dist/src/react-native/glob.d.ts +3 -0
  20. package/dist/src/react-native/glob.js +18 -0
  21. package/dist/src/react-native/glob.js.map +1 -0
  22. package/dist/src/react-native/gradle.d.ts +4 -0
  23. package/dist/src/react-native/gradle.js +49 -0
  24. package/dist/src/react-native/gradle.js.map +1 -0
  25. package/dist/src/react-native/javascript.d.ts +8 -0
  26. package/dist/src/react-native/javascript.js +25 -0
  27. package/dist/src/react-native/javascript.js.map +1 -0
  28. package/dist/src/react-native/options.d.ts +4 -0
  29. package/dist/src/react-native/options.js +3 -0
  30. package/dist/src/react-native/options.js.map +1 -0
  31. package/dist/src/react-native/react-native-wizard.d.ts +9 -0
  32. package/dist/src/react-native/react-native-wizard.js +356 -0
  33. package/dist/src/react-native/react-native-wizard.js.map +1 -0
  34. package/dist/src/react-native/uninstall.d.ts +2 -0
  35. package/dist/src/react-native/uninstall.js +130 -0
  36. package/dist/src/react-native/uninstall.js.map +1 -0
  37. package/dist/src/react-native/xcode.d.ts +18 -0
  38. package/dist/src/react-native/xcode.js +170 -0
  39. package/dist/src/react-native/xcode.js.map +1 -0
  40. package/dist/src/remix/remix-wizard.js +1 -1
  41. package/dist/src/remix/remix-wizard.js.map +1 -1
  42. package/dist/src/sourcemaps/tools/nextjs.js +3 -3
  43. package/dist/src/sourcemaps/tools/nextjs.js.map +1 -1
  44. package/dist/src/sourcemaps/tools/sentry-cli.js +1 -1
  45. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  46. package/dist/src/sveltekit/sveltekit-wizard.js +1 -1
  47. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  48. package/dist/src/utils/clack-utils.d.ts +19 -3
  49. package/dist/src/utils/clack-utils.js +141 -39
  50. package/dist/src/utils/clack-utils.js.map +1 -1
  51. package/dist/src/utils/semver.d.ts +5 -0
  52. package/dist/src/utils/semver.js +27 -0
  53. package/dist/src/utils/semver.js.map +1 -0
  54. package/dist/src/utils/sentrycli-utils.js +4 -1
  55. package/dist/src/utils/sentrycli-utils.js.map +1 -1
  56. package/dist/src/utils/types.d.ts +3 -0
  57. package/dist/src/utils/types.js.map +1 -1
  58. package/dist/test/react-native/gradle.test.js +57 -0
  59. package/dist/test/react-native/gradle.test.js.map +1 -0
  60. package/dist/test/react-native/javascript.test.js +47 -0
  61. package/dist/test/react-native/javascript.test.js.map +1 -0
  62. package/dist/test/react-native/xcode.test.d.ts +1 -0
  63. package/dist/test/react-native/xcode.test.js +144 -0
  64. package/dist/test/react-native/xcode.test.js.map +1 -0
  65. package/lib/Steps/ChooseIntegration.ts +1 -1
  66. package/lib/Steps/Integrations/Cordova.ts +7 -0
  67. package/lib/Steps/Integrations/ReactNative.ts +17 -573
  68. package/package.json +1 -1
  69. package/src/android/android-wizard.ts +3 -18
  70. package/src/apple/apple-wizard.ts +12 -3
  71. package/src/apple/cocoapod.ts +20 -9
  72. package/src/nextjs/nextjs-wizard.ts +1 -1
  73. package/src/react-native/glob.ts +13 -0
  74. package/src/react-native/gradle.ts +26 -0
  75. package/src/react-native/javascript.ts +33 -0
  76. package/src/react-native/options.ts +5 -0
  77. package/src/react-native/react-native-wizard.ts +369 -0
  78. package/src/react-native/uninstall.ts +107 -0
  79. package/src/react-native/xcode.ts +228 -0
  80. package/src/remix/remix-wizard.ts +2 -2
  81. package/src/sourcemaps/tools/nextjs.ts +6 -6
  82. package/src/sourcemaps/tools/sentry-cli.ts +1 -1
  83. package/src/sveltekit/sveltekit-wizard.ts +1 -1
  84. package/src/utils/clack-utils.ts +229 -74
  85. package/src/utils/semver.ts +33 -0
  86. package/src/utils/sentrycli-utils.ts +3 -1
  87. package/src/utils/types.ts +3 -0
  88. package/test/react-native/gradle.test.ts +310 -0
  89. package/test/react-native/javascript.test.ts +131 -0
  90. package/test/react-native/xcode.test.ts +238 -0
  91. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js +0 -198
  92. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js.map +0 -1
  93. package/dist/lib/__tests__/Setup.js +0 -57
  94. package/dist/lib/__tests__/Setup.js.map +0 -1
  95. package/lib/Steps/Integrations/__tests__/ReactNative.ts +0 -136
  96. package/lib/__tests__/Setup.ts +0 -42
  97. /package/dist/{lib/Steps/Integrations/__tests__/ReactNative.d.ts → test/react-native/gradle.test.d.ts} +0 -0
  98. /package/dist/{lib/__tests__/Setup.d.ts → test/react-native/javascript.test.d.ts} +0 -0
@@ -0,0 +1,228 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4
+ import * as fs from 'fs';
5
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
6
+ import clack from '@clack/prompts';
7
+ import chalk from 'chalk';
8
+
9
+ type BuildPhase = { shellScript: string };
10
+ type BuildPhaseMap = Record<string, BuildPhase>;
11
+
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ export function getValidExistingBuildPhases(xcodeProject: any): BuildPhaseMap {
14
+ const map: BuildPhaseMap = {};
15
+ const raw = xcodeProject.hash.project.objects.PBXShellScriptBuildPhase || {};
16
+ for (const key in raw) {
17
+ const val = raw[key];
18
+ val.isa && (map[key] = val);
19
+ }
20
+
21
+ return map;
22
+ }
23
+
24
+ export function patchBundlePhase(bundlePhase: BuildPhase | undefined) {
25
+ if (!bundlePhase) {
26
+ clack.log.warn(
27
+ `Could not find ${chalk.cyan(
28
+ 'Bundle React Native code and images',
29
+ )} build phase.`,
30
+ );
31
+ return;
32
+ }
33
+
34
+ const bundlePhaseIncludesSentry = doesBundlePhaseIncludeSentry(bundlePhase);
35
+ if (bundlePhaseIncludesSentry) {
36
+ clack.log.warn(
37
+ `Build phase ${chalk.cyan(
38
+ 'Bundle React Native code and images',
39
+ )} already includes Sentry.`,
40
+ );
41
+ return;
42
+ }
43
+
44
+ const script: string = JSON.parse(bundlePhase.shellScript);
45
+ bundlePhase.shellScript = JSON.stringify(
46
+ addSentryToBundleShellScript(script),
47
+ );
48
+ clack.log.success(
49
+ `Patched Build phase ${chalk.cyan('Bundle React Native code and images')}.`,
50
+ );
51
+ }
52
+
53
+ export function unPatchBundlePhase(bundlePhase: BuildPhase | undefined) {
54
+ if (!bundlePhase) {
55
+ clack.log.warn(
56
+ `Could not find ${chalk.cyan(
57
+ 'Bundle React Native code and images',
58
+ )} build phase.`,
59
+ );
60
+ return;
61
+ }
62
+
63
+ if (!bundlePhase.shellScript.match(/sentry-cli\s+react-native\s+xcode/i)) {
64
+ clack.log.success(
65
+ `Build phase ${chalk.cyan(
66
+ 'Bundle React Native code and images',
67
+ )} does not include Sentry.`,
68
+ );
69
+ return;
70
+ }
71
+
72
+ bundlePhase.shellScript = JSON.stringify(
73
+ removeSentryFromBundleShellScript(
74
+ <string>JSON.parse(bundlePhase.shellScript),
75
+ ),
76
+ );
77
+ clack.log.success(
78
+ `Build phase ${chalk.cyan(
79
+ 'Bundle React Native code and images',
80
+ )} unpatched successfully.`,
81
+ );
82
+ }
83
+
84
+ export function removeSentryFromBundleShellScript(script: string): string {
85
+ return (
86
+ script
87
+ // remove sentry properties export
88
+ .replace(/^export SENTRY_PROPERTIES=sentry.properties\r?\n/m, '')
89
+ .replace(
90
+ /^\/bin\/sh .*?..\/node_modules\/@sentry\/react-native\/scripts\/collect-modules.sh"?\r?\n/m,
91
+ '',
92
+ )
93
+ // unwrap react-native-xcode.sh command. In case someone replaced it
94
+ // entirely with the sentry-cli command we need to put the original
95
+ // version back in.
96
+ .replace(
97
+ /\.\.\/node_modules\/@sentry\/cli\/bin\/sentry-cli\s+react-native\s+xcode\s+\$REACT_NATIVE_XCODE/i,
98
+ '$REACT_NATIVE_XCODE',
99
+ )
100
+ );
101
+ }
102
+
103
+ export function findBundlePhase(buildPhases: BuildPhaseMap) {
104
+ return Object.values(buildPhases).find((buildPhase) =>
105
+ buildPhase.shellScript.match(/\/scripts\/react-native-xcode\.sh/i),
106
+ );
107
+ }
108
+
109
+ export function doesBundlePhaseIncludeSentry(buildPhase: BuildPhase) {
110
+ return !!buildPhase.shellScript.match(/sentry-cli\s+react-native\s+xcode/i);
111
+ }
112
+
113
+ export function addSentryToBundleShellScript(script: string): string {
114
+ return (
115
+ 'export SENTRY_PROPERTIES=sentry.properties\n' +
116
+ 'export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map"\n' +
117
+ script.replace(
118
+ '$REACT_NATIVE_XCODE',
119
+ () =>
120
+ // eslint-disable-next-line no-useless-escape
121
+ '\\"../node_modules/@sentry/cli/bin/sentry-cli react-native xcode $REACT_NATIVE_XCODE\\"',
122
+ ) +
123
+ '\n/bin/sh -c "$WITH_ENVIRONMENT ../node_modules/@sentry/react-native/scripts/collect-modules.sh"\n'
124
+ );
125
+ }
126
+
127
+ export function addDebugFilesUploadPhase(
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
+ xcodeProject: any,
130
+ { debugFilesUploadPhaseExists }: { debugFilesUploadPhaseExists: boolean },
131
+ ) {
132
+ if (debugFilesUploadPhaseExists) {
133
+ clack.log.warn(
134
+ `Build phase ${chalk.cyan(
135
+ 'Upload Debug Symbols to Sentry',
136
+ )} already exists.`,
137
+ );
138
+ return;
139
+ }
140
+
141
+ xcodeProject.addBuildPhase(
142
+ [],
143
+ 'PBXShellScriptBuildPhase',
144
+ 'Upload Debug Symbols to Sentry',
145
+ null,
146
+ {
147
+ shellPath: '/bin/sh',
148
+ shellScript: `
149
+ WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh"
150
+ if [ -f "$WITH_ENVIRONMENT" ]; then
151
+ . "$WITH_ENVIRONMENT"
152
+ fi
153
+ export SENTRY_PROPERTIES=sentry.properties
154
+ [ "$SENTRY_INCLUDE_NATIVE_SOURCES" = "true" ] && INCLUDE_SOURCES_FLAG="--include-sources" || INCLUDE_SOURCES_FLAG=""
155
+ ../node_modules/@sentry/cli/bin/sentry-cli debug-files upload "$INCLUDE_SOURCES_FLAG" "$DWARF_DSYM_FOLDER_PATH"
156
+ `,
157
+ },
158
+ );
159
+ clack.log.success(
160
+ `Added Build phase ${chalk.cyan('Upload Debug Symbols to Sentry')}.`,
161
+ );
162
+ }
163
+
164
+ export function unPatchDebugFilesUploadPhase(
165
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
+ xcodeProject: any,
167
+ ) {
168
+ const buildPhasesMap =
169
+ xcodeProject.hash.project.objects.PBXShellScriptBuildPhase || {};
170
+
171
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
172
+ const debugFilesUploadPhaseResult = findDebugFilesUploadPhase(buildPhasesMap);
173
+ if (!debugFilesUploadPhaseResult) {
174
+ clack.log.success(
175
+ `Build phase ${chalk.cyan('Upload Debug Symbols to Sentry')} not found.`,
176
+ );
177
+ return;
178
+ }
179
+
180
+ const [debugFilesUploadPhaseKey] = debugFilesUploadPhaseResult;
181
+ const firstTarget: string = xcodeProject.getFirstTarget().uuid;
182
+ const nativeTargets = xcodeProject.hash.project.objects.PBXNativeTarget;
183
+
184
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
185
+ delete buildPhasesMap[debugFilesUploadPhaseKey];
186
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
187
+ delete buildPhasesMap[`${debugFilesUploadPhaseKey}_comment`];
188
+ const phases = nativeTargets[firstTarget].buildPhases;
189
+ if (phases) {
190
+ for (let i = 0; i < phases.length; i++) {
191
+ if (phases[i].value === debugFilesUploadPhaseKey) {
192
+ phases.splice(i, 1);
193
+ break;
194
+ }
195
+ }
196
+ }
197
+ clack.log.success(
198
+ `Build phase ${chalk.cyan(
199
+ 'Upload Debug Symbols to Sentry',
200
+ )} removed successfully.`,
201
+ );
202
+ }
203
+
204
+ export function findDebugFilesUploadPhase(
205
+ buildPhasesMap: Record<string, BuildPhase>,
206
+ ): [key: string, buildPhase: BuildPhase] | undefined {
207
+ return Object.entries(buildPhasesMap).find(
208
+ ([_, buildPhase]) =>
209
+ typeof buildPhase !== 'string' &&
210
+ !!buildPhase.shellScript.match(
211
+ /sentry-cli\s+(upload-dsym|debug-files upload)\b/,
212
+ ),
213
+ );
214
+ }
215
+
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ export function writeXcodeProject(xcodeProjectPath: string, xcodeProject: any) {
218
+ const newContent = xcodeProject.writeSync();
219
+ const currentContent = fs.readFileSync(xcodeProjectPath, 'utf-8');
220
+ if (newContent === currentContent) {
221
+ return;
222
+ }
223
+
224
+ fs.writeFileSync(xcodeProjectPath, newContent, 'utf-8');
225
+ clack.log.success(
226
+ chalk.green(`Xcode project ${chalk.cyan(xcodeProjectPath)} changes saved.`),
227
+ );
228
+ }
@@ -11,7 +11,7 @@ import {
11
11
  installPackage,
12
12
  isUsingTypeScript,
13
13
  printWelcome,
14
- sourceMapsCliSetupConfig,
14
+ rcCliSetupConfig,
15
15
  } from '../utils/clack-utils';
16
16
  import { hasPackageInstalled } from '../utils/package-json';
17
17
  import { WizardOptions } from '../utils/types';
@@ -68,7 +68,7 @@ async function runRemixWizardWithTelemetry(
68
68
  const isTS = isUsingTypeScript();
69
69
  const isV2 = isRemixV2(remixConfig, packageJson);
70
70
 
71
- await addSentryCliConfig(authToken, sourceMapsCliSetupConfig);
71
+ await addSentryCliConfig({ authToken }, rcCliSetupConfig);
72
72
 
73
73
  await traceStep('Update build script for sourcemap uploads', async () => {
74
74
  try {
@@ -19,14 +19,14 @@ const getCodeSnippet = (options: SourceMapUploadToolConfigurationOptions) =>
19
19
  const nextConfig = {
20
20
  // your existing next config
21
21
  };
22
-
22
+
23
23
  ${chalk.greenBright(`const sentryWebpackPluginOptions = {
24
24
  org: "${options.orgSlug}",
25
25
  project: "${options.projectSlug}",${
26
26
  options.selfHosted ? `\n url: "${options.url}",` : ''
27
27
  }
28
28
  };`)}
29
-
29
+
30
30
  ${chalk.greenBright(`const sentryOptions = {
31
31
  // Upload additional client files (increases upload size)
32
32
  widenClientFileUpload: true,
@@ -34,12 +34,12 @@ const getCodeSnippet = (options: SourceMapUploadToolConfigurationOptions) =>
34
34
  // Hides source maps from generated client bundles
35
35
  hideSourceMaps: true,
36
36
  };`)}
37
-
37
+
38
38
  ${chalk.greenBright(`module.exports = withSentryConfig(
39
39
  nextConfig,
40
40
  sentryWebpackPluginOptions,
41
41
  sentryOptions
42
- );`)}
42
+ );`)}
43
43
  `);
44
44
 
45
45
  export const configureNextJsSourceMapsUpload = async (
@@ -99,7 +99,7 @@ In case you already tried the wizard, we can also show you how to configure your
99
99
  );
100
100
 
101
101
  await traceStep('nextjs-manual-sentryclirc', () =>
102
- addSentryCliConfig(options.authToken),
102
+ addSentryCliConfig({ authToken: options.authToken }),
103
103
  );
104
104
  }
105
105
 
@@ -108,7 +108,7 @@ In case you already tried the wizard, we can also show you how to configure your
108
108
 
109
109
  Uploading Source Maps:
110
110
  https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-source-maps
111
-
111
+
112
112
  Troubleshooting Source Maps:
113
113
  https://docs.sentry.io/platforms/javascript/guides/nextjs/troubleshooting/`);
114
114
  };
@@ -96,7 +96,7 @@ export async function configureSentryCLI(
96
96
  );
97
97
  }
98
98
 
99
- await addSentryCliConfig(options.authToken);
99
+ await addSentryCliConfig({ authToken: options.authToken });
100
100
  }
101
101
 
102
102
  export async function setupNpmScriptInCI(): Promise<void> {
@@ -95,7 +95,7 @@ export async function runSvelteKitWizardWithTelemetry(
95
95
  alreadyInstalled: sdkAlreadyInstalled,
96
96
  });
97
97
 
98
- await addSentryCliConfig(authToken);
98
+ await addSentryCliConfig({ authToken });
99
99
 
100
100
  const svelteConfig = await traceStep('load-svelte-config', loadSvelteConfig);
101
101
 
@@ -19,6 +19,7 @@ import {
19
19
  packageManagers,
20
20
  } from './package-manager';
21
21
  import { debug } from './debug';
22
+ import { fulfillsVersionRange } from './semver';
22
23
 
23
24
  const opn = require('opn') as (
24
25
  url: string,
@@ -40,17 +41,29 @@ interface WizardProjectData {
40
41
  export interface CliSetupConfig {
41
42
  filename: string;
42
43
  name: string;
44
+ gitignore: boolean;
43
45
 
44
46
  likelyAlreadyHasAuthToken(contents: string): boolean;
45
47
  tokenContent(authToken: string): string;
46
48
 
47
49
  likelyAlreadyHasOrgAndProject(contents: string): boolean;
48
50
  orgAndProjContent(org: string, project: string): string;
51
+
52
+ likelyAlreadyHasUrl?(contents: string): boolean;
53
+ urlContent?(url: string): string;
54
+ }
55
+
56
+ export interface CliSetupConfigContent {
57
+ authToken: string;
58
+ org?: string;
59
+ project?: string;
60
+ url?: string;
49
61
  }
50
62
 
51
- export const sourceMapsCliSetupConfig: CliSetupConfig = {
63
+ export const rcCliSetupConfig: CliSetupConfig = {
52
64
  filename: SENTRY_CLI_RC_FILE,
53
65
  name: 'source maps',
66
+ gitignore: true,
54
67
  likelyAlreadyHasAuthToken: function (contents: string): boolean {
55
68
  return !!(contents.includes('[auth]') && contents.match(/token=./g));
56
69
  },
@@ -69,6 +82,33 @@ export const sourceMapsCliSetupConfig: CliSetupConfig = {
69
82
  },
70
83
  };
71
84
 
85
+ export const propertiesCliSetupConfig: Required<CliSetupConfig> = {
86
+ filename: SENTRY_PROPERTIES_FILE,
87
+ gitignore: true,
88
+ name: 'debug files',
89
+ likelyAlreadyHasAuthToken(contents: string): boolean {
90
+ return !!contents.match(/auth\.token=./g);
91
+ },
92
+ tokenContent(authToken: string): string {
93
+ return `auth.token=${authToken}`;
94
+ },
95
+ likelyAlreadyHasOrgAndProject(contents: string): boolean {
96
+ return !!(
97
+ contents.match(/defaults\.org=./g) &&
98
+ contents.match(/defaults\.project=./g)
99
+ );
100
+ },
101
+ orgAndProjContent(org: string, project: string): string {
102
+ return `defaults.org=${org}\ndefaults.project=${project}`;
103
+ },
104
+ likelyAlreadyHasUrl(contents: string): boolean {
105
+ return !!contents.match(/defaults\.url=./g);
106
+ },
107
+ urlContent(url: string): string {
108
+ return `defaults.url=${url}`;
109
+ },
110
+ };
111
+
72
112
  export async function abort(message?: string, status?: number): Promise<never> {
73
113
  clack.outro(message ?? 'Wizard setup cancelled.');
74
114
  const sentryHub = Sentry.getCurrentHub();
@@ -248,6 +288,58 @@ export async function askForItemSelection(
248
288
  return selection;
249
289
  }
250
290
 
291
+ export async function confirmContinueIfPackageVersionNotSupported({
292
+ packageId,
293
+ packageName,
294
+ packageVersion,
295
+ acceptableVersions,
296
+ }: {
297
+ packageId: string;
298
+ packageName: string;
299
+ packageVersion: string;
300
+ acceptableVersions: string;
301
+ }): Promise<void> {
302
+ return traceStep(`check-package-version`, async () => {
303
+ Sentry.setTag(`${packageName.toLowerCase()}-version`, packageVersion);
304
+ const isSupportedVersion = fulfillsVersionRange({
305
+ acceptableVersions,
306
+ version: packageVersion,
307
+ canBeLatest: true,
308
+ });
309
+
310
+ if (isSupportedVersion) {
311
+ Sentry.setTag(`${packageName.toLowerCase()}-supported`, true);
312
+ return;
313
+ }
314
+
315
+ clack.log.warn(
316
+ `You have an unsupported version of ${packageName} installed:
317
+
318
+ ${packageId}@${packageVersion}`,
319
+ );
320
+
321
+ clack.note(
322
+ `Please upgrade to ${acceptableVersions} if you wish to use the Sentry Wizard.
323
+ Or setup using ${chalk.cyan(
324
+ 'https://docs.sentry.io/platforms/react-native/manual-setup/manual-setup/',
325
+ )}`,
326
+ );
327
+ const continueWithUnsupportedVersion = await abortIfCancelled(
328
+ clack.confirm({
329
+ message: 'Do you want to continue anyway?',
330
+ }),
331
+ );
332
+ Sentry.setTag(
333
+ `${packageName.toLowerCase()}-continue-with-unsupported-version`,
334
+ continueWithUnsupportedVersion,
335
+ );
336
+
337
+ if (!continueWithUnsupportedVersion) {
338
+ await abort(undefined, 0);
339
+ }
340
+ });
341
+ }
342
+
251
343
  export async function installPackage({
252
344
  packageName,
253
345
  alreadyInstalled,
@@ -306,80 +398,143 @@ export async function installPackage({
306
398
  }
307
399
 
308
400
  export async function addSentryCliConfig(
309
- authToken: string,
310
- setupConfig: CliSetupConfig = sourceMapsCliSetupConfig,
401
+ { authToken, org, project, url }: CliSetupConfigContent,
402
+ setupConfig: CliSetupConfig = rcCliSetupConfig,
311
403
  ): Promise<void> {
312
404
  return traceStep('add-sentry-cli-config', async () => {
313
- const configExists = fs.existsSync(
314
- path.join(process.cwd(), setupConfig.filename),
405
+ const configPath = path.join(process.cwd(), setupConfig.filename);
406
+ const configExists = fs.existsSync(configPath);
407
+
408
+ let configContents =
409
+ (configExists && fs.readFileSync(configPath, 'utf8')) || '';
410
+ configContents = addAuthTokenToSentryConfig(
411
+ configContents,
412
+ authToken,
413
+ setupConfig,
315
414
  );
316
- if (configExists) {
317
- const configContents = fs.readFileSync(
318
- path.join(process.cwd(), setupConfig.filename),
319
- 'utf8',
415
+ configContents = addOrgAndProjectToSentryConfig(
416
+ configContents,
417
+ org,
418
+ project,
419
+ setupConfig,
420
+ );
421
+ configContents = addUrlToSentryConfig(configContents, url, setupConfig);
422
+
423
+ try {
424
+ await fs.promises.writeFile(configPath, configContents, {
425
+ encoding: 'utf8',
426
+ flag: 'w',
427
+ });
428
+ clack.log.success(
429
+ `${configExists ? 'Saved' : 'Created'} ${chalk.cyan(
430
+ setupConfig.filename,
431
+ )}.`,
320
432
  );
433
+ } catch {
434
+ clack.log.warning(
435
+ `Failed to add auth token to ${chalk.cyan(
436
+ setupConfig.filename,
437
+ )}. Uploading ${
438
+ setupConfig.name
439
+ } during build will likely not work locally.`,
440
+ );
441
+ }
321
442
 
322
- if (setupConfig.likelyAlreadyHasAuthToken(configContents)) {
323
- clack.log.warn(
324
- `${chalk.bold(
325
- setupConfig.filename,
326
- )} already has auth token. Will not add one.`,
327
- );
328
- } else {
329
- try {
330
- await fs.promises.writeFile(
331
- path.join(process.cwd(), setupConfig.filename),
332
- `${configContents}\n${setupConfig.tokenContent(authToken)}\n`,
333
- { encoding: 'utf8', flag: 'w' },
334
- );
335
- clack.log.success(
336
- chalk.greenBright(
337
- `Added auth token to ${chalk.bold(
338
- setupConfig.filename,
339
- )} for you to test uploading ${setupConfig.name} locally.`,
340
- ),
341
- );
342
- } catch {
343
- clack.log.warning(
344
- `Failed to add auth token to ${chalk.bold(
345
- setupConfig.filename,
346
- )}. Uploading ${
347
- setupConfig.name
348
- } during build will likely not work locally.`,
349
- );
350
- }
351
- }
443
+ if (setupConfig.gitignore) {
444
+ await addCliConfigFileToGitIgnore(setupConfig.filename);
352
445
  } else {
353
- try {
354
- await fs.promises.writeFile(
355
- path.join(process.cwd(), setupConfig.filename),
356
- `${setupConfig.tokenContent(authToken)}\n`,
357
- { encoding: 'utf8', flag: 'w' },
358
- );
359
- clack.log.success(
360
- chalk.greenBright(
361
- `Created ${chalk.bold(
362
- setupConfig.filename,
363
- )} with auth token for you to test uploading ${
364
- setupConfig.name
365
- } locally.`,
366
- ),
367
- );
368
- } catch {
369
- clack.log.warning(
370
- `Failed to create ${chalk.bold(
371
- setupConfig.filename,
372
- )} with auth token. Uploading ${
373
- setupConfig.name
374
- } during build will likely not work locally.`,
375
- );
376
- }
446
+ clack.log.warn(
447
+ chalk.yellow('DO NOT commit auth token to your repository!'),
448
+ );
377
449
  }
378
-
379
- await addAuthTokenFileToGitIgnore(setupConfig.filename);
380
450
  });
381
451
  }
382
452
 
453
+ function addAuthTokenToSentryConfig(
454
+ configContents: string,
455
+ authToken: string | undefined,
456
+ setupConfig: CliSetupConfig,
457
+ ): string {
458
+ if (!authToken) {
459
+ return configContents;
460
+ }
461
+
462
+ if (setupConfig.likelyAlreadyHasAuthToken(configContents)) {
463
+ clack.log.warn(
464
+ `${chalk.cyan(
465
+ setupConfig.filename,
466
+ )} already has auth token. Will not add one.`,
467
+ );
468
+ return configContents;
469
+ }
470
+
471
+ const newContents = `${configContents}\n${setupConfig.tokenContent(
472
+ authToken,
473
+ )}\n`;
474
+ clack.log.success(
475
+ `Added auth token to ${chalk.cyan(
476
+ setupConfig.filename,
477
+ )} for you to test uploading ${setupConfig.name} locally.`,
478
+ );
479
+ return newContents;
480
+ }
481
+
482
+ function addOrgAndProjectToSentryConfig(
483
+ configContents: string,
484
+ org: string | undefined,
485
+ project: string | undefined,
486
+ setupConfig: CliSetupConfig,
487
+ ): string {
488
+ if (!org || !project) {
489
+ return configContents;
490
+ }
491
+
492
+ if (setupConfig.likelyAlreadyHasOrgAndProject(configContents)) {
493
+ clack.log.warn(
494
+ `${chalk.cyan(
495
+ setupConfig.filename,
496
+ )} already has org and project. Will not add them.`,
497
+ );
498
+ return configContents;
499
+ }
500
+
501
+ const newContents = `${configContents}\n${setupConfig.orgAndProjContent(
502
+ org,
503
+ project,
504
+ )}\n`;
505
+ clack.log.success(
506
+ `Added default org and project to ${chalk.cyan(
507
+ setupConfig.filename,
508
+ )} for you to test uploading ${setupConfig.name} locally.`,
509
+ );
510
+ return newContents;
511
+ }
512
+
513
+ function addUrlToSentryConfig(
514
+ configContents: string,
515
+ url: string | undefined,
516
+ setupConfig: CliSetupConfig,
517
+ ): string {
518
+ if (!url || !setupConfig.urlContent || !setupConfig.likelyAlreadyHasUrl) {
519
+ return configContents;
520
+ }
521
+
522
+ if (setupConfig.likelyAlreadyHasUrl(configContents)) {
523
+ clack.log.warn(
524
+ `${chalk.cyan(setupConfig.filename)} already has url. Will not add one.`,
525
+ );
526
+ return configContents;
527
+ }
528
+
529
+ const newContents = `${configContents}\n${setupConfig.urlContent(url)}\n`;
530
+ clack.log.success(
531
+ `Added default url to ${chalk.cyan(
532
+ setupConfig.filename,
533
+ )} for you to test uploading ${setupConfig.name} locally.`,
534
+ );
535
+ return newContents;
536
+ }
537
+
383
538
  export async function addDotEnvSentryBuildPluginFile(
384
539
  authToken: string,
385
540
  ): Promise<void> {
@@ -447,25 +602,23 @@ SENTRY_AUTH_TOKEN="${authToken}"
447
602
  }
448
603
  }
449
604
 
450
- await addAuthTokenFileToGitIgnore(SENTRY_DOT_ENV_FILE);
605
+ await addCliConfigFileToGitIgnore(SENTRY_DOT_ENV_FILE);
451
606
  }
452
607
 
453
- async function addAuthTokenFileToGitIgnore(filename: string): Promise<void> {
608
+ async function addCliConfigFileToGitIgnore(filename: string): Promise<void> {
454
609
  //TODO: Add a check to see if the file is already ignored in .gitignore
455
610
  try {
456
611
  await fs.promises.appendFile(
457
612
  path.join(process.cwd(), '.gitignore'),
458
- `\n# Sentry Auth Token\n${filename}\n`,
613
+ `\n# Sentry Config File\n${filename}\n`,
459
614
  { encoding: 'utf8' },
460
615
  );
461
616
  clack.log.success(
462
- chalk.greenBright(
463
- `Added ${chalk.bold(filename)} to ${chalk.bold('.gitignore')}.`,
464
- ),
617
+ `Added ${chalk.cyan(filename)} to ${chalk.cyan('.gitignore')}.`,
465
618
  );
466
619
  } catch {
467
620
  clack.log.error(
468
- `Failed adding ${chalk.bold(filename)} to ${chalk.bold(
621
+ `Failed adding ${chalk.cyan(filename)} to ${chalk.cyan(
469
622
  '.gitignore',
470
623
  )}. Please add it manually!`,
471
624
  );
@@ -585,7 +738,8 @@ export async function getOrAskForProjectData(
585
738
  | 'javascript-remix'
586
739
  | 'javascript-sveltekit'
587
740
  | 'apple-ios'
588
- | 'android',
741
+ | 'android'
742
+ | 'react-native',
589
743
  ): Promise<{
590
744
  sentryUrl: string;
591
745
  selfHosted: boolean;
@@ -713,7 +867,8 @@ async function askForWizardLogin(options: {
713
867
  | 'javascript-remix'
714
868
  | 'javascript-sveltekit'
715
869
  | 'apple-ios'
716
- | 'android';
870
+ | 'android'
871
+ | 'react-native';
717
872
  }): Promise<WizardProjectData> {
718
873
  Sentry.setTag('has-promo-code', !!options.promoCode);
719
874