@sentry/wizard 3.9.2 → 3.11.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 (168) hide show
  1. package/CHANGELOG.md +58 -6
  2. package/dist/lib/Constants.d.ts +2 -0
  3. package/dist/lib/Constants.js +10 -0
  4. package/dist/lib/Constants.js.map +1 -1
  5. package/dist/lib/Steps/ChooseIntegration.js +15 -4
  6. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  7. package/dist/lib/Steps/Integrations/Android.d.ts +9 -0
  8. package/dist/lib/Steps/Integrations/Android.js +86 -0
  9. package/dist/lib/Steps/Integrations/Android.js.map +1 -0
  10. package/dist/lib/Steps/Integrations/Cordova.js +5 -1
  11. package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
  12. package/dist/lib/Steps/Integrations/ReactNative.js +3 -3
  13. package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
  14. package/dist/lib/Steps/Integrations/Remix.d.ts +12 -0
  15. package/dist/lib/Steps/Integrations/Remix.js +98 -0
  16. package/dist/lib/Steps/Integrations/Remix.js.map +1 -0
  17. package/dist/lib/Steps/PromptForParameters.js +36 -3
  18. package/dist/lib/Steps/PromptForParameters.js.map +1 -1
  19. package/dist/lib/Steps/SentryProjectSelector.js +1 -1
  20. package/dist/lib/Steps/SentryProjectSelector.js.map +1 -1
  21. package/dist/package.json +4 -3
  22. package/dist/src/android/android-wizard.d.ts +2 -0
  23. package/dist/src/android/android-wizard.js +217 -0
  24. package/dist/src/android/android-wizard.js.map +1 -0
  25. package/dist/src/android/code-tools.d.ts +39 -0
  26. package/dist/src/android/code-tools.js +161 -0
  27. package/dist/src/android/code-tools.js.map +1 -0
  28. package/dist/src/android/gradle.d.ts +62 -0
  29. package/dist/src/android/gradle.js +281 -0
  30. package/dist/src/android/gradle.js.map +1 -0
  31. package/dist/src/android/manifest.d.ts +57 -0
  32. package/dist/src/android/manifest.js +183 -0
  33. package/dist/src/android/manifest.js.map +1 -0
  34. package/dist/src/android/templates.d.ts +11 -0
  35. package/dist/src/android/templates.js +34 -0
  36. package/dist/src/android/templates.js.map +1 -0
  37. package/dist/src/apple/apple-wizard.js +123 -64
  38. package/dist/src/apple/apple-wizard.js.map +1 -1
  39. package/dist/src/apple/cocoapod.js +4 -3
  40. package/dist/src/apple/cocoapod.js.map +1 -1
  41. package/dist/src/apple/code-tools.d.ts +1 -1
  42. package/dist/src/apple/code-tools.js +43 -19
  43. package/dist/src/apple/code-tools.js.map +1 -1
  44. package/dist/src/apple/fastlane.d.ts +1 -1
  45. package/dist/src/apple/fastlane.js +12 -6
  46. package/dist/src/apple/fastlane.js.map +1 -1
  47. package/dist/src/apple/templates.d.ts +2 -2
  48. package/dist/src/apple/templates.js +4 -4
  49. package/dist/src/apple/templates.js.map +1 -1
  50. package/dist/src/apple/xcode-manager.d.ts +19 -3
  51. package/dist/src/apple/xcode-manager.js +126 -24
  52. package/dist/src/apple/xcode-manager.js.map +1 -1
  53. package/dist/src/nextjs/nextjs-wizard.js +49 -11
  54. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  55. package/dist/src/nextjs/templates.d.ts +2 -0
  56. package/dist/src/nextjs/templates.js +6 -2
  57. package/dist/src/nextjs/templates.js.map +1 -1
  58. package/dist/src/remix/codemods/handle-error.d.ts +2 -0
  59. package/dist/src/remix/codemods/handle-error.js +70 -0
  60. package/dist/src/remix/codemods/handle-error.js.map +1 -0
  61. package/dist/src/remix/codemods/root-v1.d.ts +1 -0
  62. package/dist/src/remix/codemods/root-v1.js +133 -0
  63. package/dist/src/remix/codemods/root-v1.js.map +1 -0
  64. package/dist/src/remix/codemods/root-v2.d.ts +1 -0
  65. package/dist/src/remix/codemods/root-v2.js +134 -0
  66. package/dist/src/remix/codemods/root-v2.js.map +1 -0
  67. package/dist/src/remix/remix-wizard.d.ts +2 -0
  68. package/dist/src/remix/remix-wizard.js +196 -0
  69. package/dist/src/remix/remix-wizard.js.map +1 -0
  70. package/dist/src/remix/sdk-setup.d.ts +18 -0
  71. package/dist/src/remix/sdk-setup.js +293 -0
  72. package/dist/src/remix/sdk-setup.js.map +1 -0
  73. package/dist/src/remix/templates.d.ts +2 -0
  74. package/dist/src/remix/templates.js +6 -0
  75. package/dist/src/remix/templates.js.map +1 -0
  76. package/dist/src/remix/utils.d.ts +6 -0
  77. package/dist/src/remix/utils.js +55 -0
  78. package/dist/src/remix/utils.js.map +1 -0
  79. package/dist/src/sourcemaps/sourcemaps-wizard.js +49 -25
  80. package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
  81. package/dist/src/sourcemaps/tools/nextjs.js +1 -1
  82. package/dist/src/sourcemaps/tools/nextjs.js.map +1 -1
  83. package/dist/src/sourcemaps/tools/remix.d.ts +3 -0
  84. package/dist/src/sourcemaps/tools/remix.js +125 -0
  85. package/dist/src/sourcemaps/tools/remix.js.map +1 -0
  86. package/dist/src/sourcemaps/tools/sentry-cli.js +19 -16
  87. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  88. package/dist/src/sourcemaps/tools/vite.d.ts +2 -1
  89. package/dist/src/sourcemaps/tools/vite.js +99 -12
  90. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  91. package/dist/src/sourcemaps/utils/detect-tool.d.ts +1 -1
  92. package/dist/src/sourcemaps/utils/detect-tool.js +1 -0
  93. package/dist/src/sourcemaps/utils/detect-tool.js.map +1 -1
  94. package/dist/src/sveltekit/sdk-setup.js +3 -3
  95. package/dist/src/sveltekit/sdk-setup.js.map +1 -1
  96. package/dist/src/sveltekit/sveltekit-wizard.js +34 -44
  97. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  98. package/dist/src/telemetry.js +1 -0
  99. package/dist/src/telemetry.js.map +1 -1
  100. package/dist/src/utils/ast-utils.d.ts +2 -2
  101. package/dist/src/utils/ast-utils.js +7 -7
  102. package/dist/src/utils/ast-utils.js.map +1 -1
  103. package/dist/src/utils/clack-utils.d.ts +23 -28
  104. package/dist/src/utils/clack-utils.js +287 -244
  105. package/dist/src/utils/clack-utils.js.map +1 -1
  106. package/dist/src/utils/package-manager.d.ts +10 -0
  107. package/dist/{lib/Helper/PackageManager.js → src/utils/package-manager.js} +42 -74
  108. package/dist/src/utils/package-manager.js.map +1 -0
  109. package/dist/src/utils/release-registry.d.ts +1 -0
  110. package/dist/src/utils/release-registry.js +68 -0
  111. package/dist/src/utils/release-registry.js.map +1 -0
  112. package/dist/src/utils/sentrycli-utils.d.ts +4 -0
  113. package/dist/src/utils/sentrycli-utils.js +41 -0
  114. package/dist/src/utils/sentrycli-utils.js.map +1 -0
  115. package/dist/test/sourcemaps/tools/vite.test.d.ts +1 -0
  116. package/dist/test/sourcemaps/tools/vite.test.js +132 -0
  117. package/dist/test/sourcemaps/tools/vite.test.js.map +1 -0
  118. package/lib/Constants.ts +10 -0
  119. package/lib/Steps/ChooseIntegration.ts +14 -3
  120. package/lib/Steps/Integrations/Android.ts +23 -0
  121. package/lib/Steps/Integrations/Cordova.ts +5 -1
  122. package/lib/Steps/Integrations/ReactNative.ts +9 -3
  123. package/lib/Steps/Integrations/Remix.ts +32 -0
  124. package/lib/Steps/PromptForParameters.ts +48 -3
  125. package/lib/Steps/SentryProjectSelector.ts +3 -1
  126. package/package.json +4 -3
  127. package/src/android/android-wizard.ts +196 -0
  128. package/src/android/code-tools.ts +156 -0
  129. package/src/android/gradle.ts +245 -0
  130. package/src/android/manifest.ts +180 -0
  131. package/src/android/templates.ts +88 -0
  132. package/src/apple/apple-wizard.ts +113 -35
  133. package/src/apple/cocoapod.ts +6 -3
  134. package/src/apple/code-tools.ts +46 -18
  135. package/src/apple/fastlane.ts +6 -12
  136. package/src/apple/templates.ts +2 -8
  137. package/src/apple/xcode-manager.ts +167 -25
  138. package/src/nextjs/nextjs-wizard.ts +72 -8
  139. package/src/nextjs/templates.ts +16 -2
  140. package/src/remix/codemods/handle-error.ts +67 -0
  141. package/src/remix/codemods/root-v1.ts +91 -0
  142. package/src/remix/codemods/root-v2.ts +84 -0
  143. package/src/remix/remix-wizard.ts +132 -0
  144. package/src/remix/sdk-setup.ts +300 -0
  145. package/src/remix/templates.ts +15 -0
  146. package/src/remix/utils.ts +41 -0
  147. package/src/sourcemaps/sourcemaps-wizard.ts +28 -5
  148. package/src/sourcemaps/tools/nextjs.ts +2 -2
  149. package/src/sourcemaps/tools/remix.ts +90 -0
  150. package/src/sourcemaps/tools/sentry-cli.ts +8 -7
  151. package/src/sourcemaps/tools/vite.ts +136 -6
  152. package/src/sourcemaps/utils/detect-tool.ts +4 -1
  153. package/src/sveltekit/sdk-setup.ts +4 -4
  154. package/src/sveltekit/sveltekit-wizard.ts +5 -14
  155. package/src/telemetry.ts +2 -0
  156. package/src/utils/ast-utils.ts +7 -5
  157. package/src/utils/clack-utils.ts +366 -258
  158. package/src/utils/package-manager.ts +61 -0
  159. package/src/utils/release-registry.ts +19 -0
  160. package/src/utils/sentrycli-utils.ts +22 -0
  161. package/test/sourcemaps/tools/vite.test.ts +149 -0
  162. package/dist/lib/Helper/PackageManager.d.ts +0 -22
  163. package/dist/lib/Helper/PackageManager.js.map +0 -1
  164. package/dist/src/utils/vendor/clack-custom-select.d.ts +0 -21
  165. package/dist/src/utils/vendor/clack-custom-select.js +0 -137
  166. package/dist/src/utils/vendor/clack-custom-select.js.map +0 -1
  167. package/lib/Helper/PackageManager.ts +0 -59
  168. package/src/utils/vendor/clack-custom-select.ts +0 -160
@@ -0,0 +1,132 @@
1
+ // @ts-expect-error - clack is ESM and TS complains about that. It works though
2
+ import clack from '@clack/prompts';
3
+ import chalk from 'chalk';
4
+
5
+ import {
6
+ addSentryCliConfig,
7
+ confirmContinueEvenThoughNoGitRepo,
8
+ ensurePackageIsInstalled,
9
+ getOrAskForProjectData,
10
+ getPackageDotJson,
11
+ installPackage,
12
+ isUsingTypeScript,
13
+ printWelcome,
14
+ sourceMapsCliSetupConfig,
15
+ } from '../utils/clack-utils';
16
+ import { hasPackageInstalled } from '../utils/package-json';
17
+ import { WizardOptions } from '../utils/types';
18
+ import {
19
+ initializeSentryOnEntryClient,
20
+ initializeSentryOnEntryServer,
21
+ updateBuildScript,
22
+ instrumentRootRoute,
23
+ isRemixV2,
24
+ loadRemixConfig,
25
+ } from './sdk-setup';
26
+ import { debug } from '../utils/debug';
27
+ import { traceStep, withTelemetry } from '../telemetry';
28
+
29
+ export async function runRemixWizard(options: WizardOptions): Promise<void> {
30
+ return withTelemetry(
31
+ {
32
+ enabled: options.telemetryEnabled,
33
+ integration: 'remix',
34
+ },
35
+ () => runRemixWizardWithTelemetry(options),
36
+ );
37
+ }
38
+
39
+ async function runRemixWizardWithTelemetry(
40
+ options: WizardOptions,
41
+ ): Promise<void> {
42
+ printWelcome({
43
+ wizardName: 'Sentry Remix Wizard',
44
+ promoCode: options.promoCode,
45
+ telemetryEnabled: options.telemetryEnabled,
46
+ });
47
+
48
+ await confirmContinueEvenThoughNoGitRepo();
49
+
50
+ const remixConfig = await loadRemixConfig();
51
+ const packageJson = await getPackageDotJson();
52
+
53
+ // We expect `@remix-run/dev` to be installed for every Remix project
54
+ await ensurePackageIsInstalled(packageJson, '@remix-run/dev', 'Remix');
55
+
56
+ const { selectedProject, authToken } = await getOrAskForProjectData(
57
+ options,
58
+ 'javascript-remix',
59
+ );
60
+
61
+ await traceStep('Install Sentry SDK', () =>
62
+ installPackage({
63
+ packageName: '@sentry/remix',
64
+ alreadyInstalled: hasPackageInstalled('@sentry/remix', packageJson),
65
+ }),
66
+ );
67
+
68
+ const dsn = selectedProject.keys[0].dsn.public;
69
+
70
+ const isTS = isUsingTypeScript();
71
+ const isV2 = isRemixV2(remixConfig, packageJson);
72
+
73
+ await addSentryCliConfig(
74
+ authToken,
75
+ sourceMapsCliSetupConfig,
76
+ selectedProject.organization.slug,
77
+ selectedProject.name,
78
+ );
79
+
80
+ await traceStep('Update build script for sourcemap uploads', async () => {
81
+ try {
82
+ await updateBuildScript();
83
+ } catch (e) {
84
+ clack.log
85
+ .warn(`Could not update build script to generate and upload sourcemaps.
86
+ Please update your build script manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/sourcemaps/`);
87
+ debug(e);
88
+ }
89
+ });
90
+
91
+ await traceStep('Instrument root route', async () => {
92
+ try {
93
+ await instrumentRootRoute(isV2, isTS);
94
+ } catch (e) {
95
+ clack.log.warn(`Could not instrument root route.
96
+ Please do it manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/`);
97
+ debug(e);
98
+ }
99
+ });
100
+
101
+ await traceStep('Initialize Sentry on client entry', async () => {
102
+ try {
103
+ await initializeSentryOnEntryClient(dsn, isTS);
104
+ } catch (e) {
105
+ clack.log.warn(`Could not initialize Sentry on client entry.
106
+ Please do it manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/`);
107
+ debug(e);
108
+ }
109
+ });
110
+
111
+ await traceStep('Initialize Sentry on server entry', async () => {
112
+ try {
113
+ await initializeSentryOnEntryServer(dsn, isTS, isV2);
114
+ } catch (e) {
115
+ clack.log.warn(`Could not initialize Sentry on server entry.
116
+ Please do it manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/`);
117
+ debug(e);
118
+ }
119
+ });
120
+
121
+ clack.outro(`
122
+ ${chalk.green(
123
+ 'Sentry has been successfully configured for your Remix project.',
124
+ )}
125
+
126
+ ${chalk.cyan('You can now deploy your project to see Sentry in action.')}
127
+
128
+ ${chalk.cyan(
129
+ `To learn more about how to use Sentry with Remix, visit our documentation:
130
+ https://docs.sentry.io/platforms/javascript/guides/remix/`,
131
+ )}`);
132
+ }
@@ -0,0 +1,300 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+
3
+ import type { Program } from '@babel/types';
4
+
5
+ // @ts-expect-error - magicast is ESM and TS complains about that. It works though
6
+ import type { ProxifiedModule } from 'magicast';
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import * as url from 'url';
11
+
12
+ // @ts-expect-error - clack is ESM and TS complains about that. It works though
13
+ import clack from '@clack/prompts';
14
+ import chalk from 'chalk';
15
+ import { parse } from 'semver';
16
+
17
+ // @ts-expect-error - magicast is ESM and TS complains about that. It works though
18
+ import { builders, generateCode, loadFile, writeFile } from 'magicast';
19
+ import { PackageDotJson, getPackageVersion } from '../utils/package-json';
20
+ import { getInitCallInsertionIndex, hasSentryContent } from './utils';
21
+ import { instrumentRootRouteV1 } from './codemods/root-v1';
22
+ import { instrumentRootRouteV2 } from './codemods/root-v2';
23
+ import { instrumentHandleError } from './codemods/handle-error';
24
+
25
+ export type PartialRemixConfig = {
26
+ unstable_dev?: boolean;
27
+ future?: {
28
+ v2_dev?: boolean;
29
+ v2_errorBoundary?: boolean;
30
+ v2_headers?: boolean;
31
+ v2_meta?: boolean;
32
+ v2_normalizeFormMethod?: boolean;
33
+ v2_routeConvention?: boolean;
34
+ };
35
+ };
36
+
37
+ const REMIX_CONFIG_FILE = 'remix.config.js';
38
+
39
+ function insertClientInitCall(
40
+ dsn: string,
41
+ originalHooksMod: ProxifiedModule<any>,
42
+ ): void {
43
+ const initCall = builders.functionCall('Sentry.init', {
44
+ dsn,
45
+ tracesSampleRate: 1.0,
46
+ replaysSessionSampleRate: 0.1,
47
+ replaysOnErrorSampleRate: 1.0,
48
+ integrations: [
49
+ builders.newExpression('Sentry.BrowserTracing', {
50
+ routingInstrumentation: builders.functionCall(
51
+ 'Sentry.remixRouterInstrumentation',
52
+ builders.raw('useEffect'),
53
+ builders.raw('useLocation'),
54
+ builders.raw('useMatches'),
55
+ ),
56
+ }),
57
+ builders.newExpression('Sentry.Replay'),
58
+ ],
59
+ });
60
+
61
+ const originalHooksModAST = originalHooksMod.$ast as Program;
62
+ const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
63
+
64
+ originalHooksModAST.body.splice(
65
+ initCallInsertionIndex,
66
+ 0,
67
+ // @ts-expect-error - string works here because the AST is proxified by magicast
68
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
69
+ generateCode(initCall).code,
70
+ );
71
+ }
72
+
73
+ function insertServerInitCall(
74
+ dsn: string,
75
+ originalHooksMod: ProxifiedModule<any>,
76
+ ) {
77
+ const initCall = builders.functionCall('Sentry.init', {
78
+ dsn,
79
+ tracesSampleRate: 1.0,
80
+ });
81
+
82
+ const originalHooksModAST = originalHooksMod.$ast as Program;
83
+
84
+ const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
85
+
86
+ originalHooksModAST.body.splice(
87
+ initCallInsertionIndex,
88
+ 0,
89
+ // @ts-expect-error - string works here because the AST is proxified by magicast
90
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
91
+ generateCode(initCall).code,
92
+ );
93
+ }
94
+
95
+ export function isRemixV2(
96
+ remixConfig: PartialRemixConfig,
97
+ packageJson: PackageDotJson,
98
+ ): boolean {
99
+ const remixVersion = getPackageVersion('@remix-run/react', packageJson);
100
+ const remixVersionMajor = remixVersion && parse(remixVersion)?.major;
101
+ const isV2Remix = remixVersionMajor && remixVersionMajor >= 2;
102
+
103
+ return isV2Remix || remixConfig?.future?.v2_errorBoundary || false;
104
+ }
105
+
106
+ export async function loadRemixConfig(): Promise<PartialRemixConfig> {
107
+ const configFilePath = path.join(process.cwd(), REMIX_CONFIG_FILE);
108
+
109
+ try {
110
+ if (!fs.existsSync(configFilePath)) {
111
+ return {};
112
+ }
113
+
114
+ const configUrl = url.pathToFileURL(configFilePath).href;
115
+ const remixConfigModule = (await import(configUrl)) as {
116
+ default: PartialRemixConfig;
117
+ };
118
+
119
+ return remixConfigModule?.default || {};
120
+ } catch (e: unknown) {
121
+ clack.log.error(`Couldn't load ${REMIX_CONFIG_FILE}.`);
122
+ clack.log.info(
123
+ chalk.dim(
124
+ typeof e === 'object' && e != null && 'toString' in e
125
+ ? e.toString()
126
+ : typeof e === 'string'
127
+ ? e
128
+ : 'Unknown error',
129
+ ),
130
+ );
131
+
132
+ return {};
133
+ }
134
+ }
135
+
136
+ export async function instrumentRootRoute(
137
+ isV2?: boolean,
138
+ isTS?: boolean,
139
+ ): Promise<void> {
140
+ const rootFilename = `root.${isTS ? 'tsx' : 'jsx'}`;
141
+
142
+ if (isV2) {
143
+ await instrumentRootRouteV2(rootFilename);
144
+ } else {
145
+ await instrumentRootRouteV1(rootFilename);
146
+ }
147
+
148
+ clack.log.success(
149
+ `Successfully instrumented root route ${chalk.cyan(rootFilename)}.`,
150
+ );
151
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access */
152
+ }
153
+
154
+ export async function updateBuildScript(): Promise<void> {
155
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
156
+ // Add sourcemaps option to build script
157
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
158
+ const packageJsonString = (
159
+ await fs.promises.readFile(packageJsonPath)
160
+ ).toString();
161
+ const packageJson = JSON.parse(packageJsonString);
162
+
163
+ if (!packageJson.scripts) {
164
+ packageJson.scripts = {};
165
+ }
166
+
167
+ if (!packageJson.scripts.build) {
168
+ packageJson.scripts.build =
169
+ 'remix build --sourcemap && sentry-upload-sourcemaps';
170
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
171
+ } else if (packageJson.scripts.build.includes('remix build')) {
172
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
173
+ packageJson.scripts.build = packageJson.scripts.build.replace(
174
+ 'remix build',
175
+ 'remix build --sourcemap && sentry-upload-sourcemaps',
176
+ );
177
+ }
178
+
179
+ await fs.promises.writeFile(
180
+ packageJsonPath,
181
+ JSON.stringify(packageJson, null, 2),
182
+ );
183
+
184
+ clack.log.success(
185
+ `Successfully updated ${chalk.cyan('build')} script in ${chalk.cyan(
186
+ 'package.json',
187
+ )} to generate and upload sourcemaps.`,
188
+ );
189
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access */
190
+ }
191
+
192
+ export async function initializeSentryOnEntryClient(
193
+ dsn: string,
194
+ isTS: boolean,
195
+ ): Promise<void> {
196
+ const clientEntryFilename = `entry.client.${isTS ? 'tsx' : 'jsx'}`;
197
+
198
+ const originalEntryClient = path.join(
199
+ process.cwd(),
200
+ 'app',
201
+ clientEntryFilename,
202
+ );
203
+
204
+ const originalEntryClientMod = await loadFile(originalEntryClient);
205
+
206
+ if (hasSentryContent(originalEntryClient, originalEntryClientMod.$code)) {
207
+ return;
208
+ }
209
+
210
+ originalEntryClientMod.imports.$add({
211
+ from: '@sentry/remix',
212
+ imported: '*',
213
+ local: 'Sentry',
214
+ });
215
+
216
+ originalEntryClientMod.imports.$add({
217
+ from: 'react',
218
+ imported: 'useEffect',
219
+ local: 'useEffect',
220
+ });
221
+
222
+ originalEntryClientMod.imports.$add({
223
+ from: '@remix-run/react',
224
+ imported: 'useLocation',
225
+ local: 'useLocation',
226
+ });
227
+
228
+ originalEntryClientMod.imports.$add({
229
+ from: '@remix-run/react',
230
+ imported: 'useMatches',
231
+ local: 'useMatches',
232
+ });
233
+
234
+ insertClientInitCall(dsn, originalEntryClientMod);
235
+
236
+ await writeFile(
237
+ originalEntryClientMod.$ast,
238
+ path.join(process.cwd(), 'app', clientEntryFilename),
239
+ );
240
+
241
+ clack.log.success(
242
+ `Successfully initialized Sentry on client entry point ${chalk.cyan(
243
+ clientEntryFilename,
244
+ )}`,
245
+ );
246
+ }
247
+
248
+ export async function initializeSentryOnEntryServer(
249
+ dsn: string,
250
+ isV2: boolean,
251
+ isTS: boolean,
252
+ ): Promise<void> {
253
+ const serverEntryFilename = `entry.server.${isTS ? 'tsx' : 'jsx'}`;
254
+
255
+ const originalEntryServer = path.join(
256
+ process.cwd(),
257
+ 'app',
258
+ serverEntryFilename,
259
+ );
260
+
261
+ const originalEntryServerMod = await loadFile(originalEntryServer);
262
+
263
+ if (hasSentryContent(originalEntryServer, originalEntryServerMod.$code)) {
264
+ return;
265
+ }
266
+
267
+ originalEntryServerMod.imports.$add({
268
+ from: '@sentry/remix',
269
+ imported: '*',
270
+ local: 'Sentry',
271
+ });
272
+
273
+ insertServerInitCall(dsn, originalEntryServerMod);
274
+
275
+ if (isV2) {
276
+ const handleErrorInstrumented = instrumentHandleError(
277
+ originalEntryServerMod,
278
+ serverEntryFilename,
279
+ );
280
+
281
+ if (handleErrorInstrumented) {
282
+ clack.log.success(
283
+ `Instrumented ${chalk.cyan('handleError')} in ${chalk.cyan(
284
+ `${serverEntryFilename}`,
285
+ )}`,
286
+ );
287
+ }
288
+ }
289
+
290
+ await writeFile(
291
+ originalEntryServerMod.$ast,
292
+ path.join(process.cwd(), 'app', serverEntryFilename),
293
+ );
294
+
295
+ clack.log.success(
296
+ `Successfully initialized Sentry on server entry point ${chalk.cyan(
297
+ serverEntryFilename,
298
+ )}.`,
299
+ );
300
+ }
@@ -0,0 +1,15 @@
1
+ export const ERROR_BOUNDARY_TEMPLATE_V2 = `const ErrorBoundary = () => {
2
+ const error = useRouteError();
3
+ captureRemixErrorBoundaryError(error);
4
+ return <div>Something went wrong</div>;
5
+ };
6
+ `;
7
+
8
+ export const HANDLE_ERROR_TEMPLATE_V2 = `function handleError(error) {
9
+ if (error instanceof Error) {
10
+ Sentry.captureRemixErrorBoundaryError(error);
11
+ } else {
12
+ Sentry.captureException(error);
13
+ }
14
+ }
15
+ `;
@@ -0,0 +1,41 @@
1
+ import type { Program } from '@babel/types';
2
+
3
+ import * as path from 'path';
4
+
5
+ // @ts-expect-error - clack is ESM and TS complains about that. It works though
6
+ import clack from '@clack/prompts';
7
+ import chalk from 'chalk';
8
+
9
+ // Copied from sveltekit wizard
10
+ export function hasSentryContent(
11
+ fileName: string,
12
+ fileContent: string,
13
+ ): boolean {
14
+ const includesContent = fileContent.includes('@sentry/remix');
15
+
16
+ if (includesContent) {
17
+ clack.log.warn(
18
+ `File ${chalk.cyan(path.basename(fileName))} already contains Sentry code.
19
+ Skipping adding Sentry functionality to ${chalk.cyan(
20
+ path.basename(fileName),
21
+ )}.`,
22
+ );
23
+ }
24
+
25
+ return includesContent;
26
+ }
27
+
28
+ /**
29
+ * We want to insert the init call on top of the file but after all import statements
30
+ */
31
+ export function getInitCallInsertionIndex(
32
+ originalHooksModAST: Program,
33
+ ): number {
34
+ for (let x = originalHooksModAST.body.length - 1; x >= 0; x--) {
35
+ if (originalHooksModAST.body[x].type === 'ImportDeclaration') {
36
+ return x + 1;
37
+ }
38
+ }
39
+
40
+ return 0;
41
+ }
@@ -4,9 +4,9 @@ import chalk from 'chalk';
4
4
  import * as Sentry from '@sentry/node';
5
5
 
6
6
  import {
7
+ abort,
7
8
  abortIfCancelled,
8
9
  confirmContinueEvenThoughNoGitRepo,
9
- detectPackageManager,
10
10
  SENTRY_DOT_ENV_FILE,
11
11
  printWelcome,
12
12
  SENTRY_CLI_RC_FILE,
@@ -29,6 +29,8 @@ import { checkIfMoreSuitableWizardExistsAndAskForRedirect } from './utils/other-
29
29
  import { configureAngularSourcemapGenerationFlow } from './tools/angular';
30
30
  import { detectUsedTool, SupportedTools } from './utils/detect-tool';
31
31
  import { configureNextJsSourceMapsUpload } from './tools/nextjs';
32
+ import { configureRemixSourceMapsUpload } from './tools/remix';
33
+ import { detectPackageManger } from '../utils/package-manager';
32
34
 
33
35
  export async function runSourcemapsWizard(
34
36
  options: WizardOptions,
@@ -88,6 +90,14 @@ You can turn this off by running the wizard with the '--disable-telemetry' flag.
88
90
 
89
91
  Sentry.setTag('selected-tool', selectedTool);
90
92
 
93
+ if (selectedTool === 'no-tool') {
94
+ clack.log.info(
95
+ "No Problem! But in this case, there's nothing to configure :)",
96
+ );
97
+ await abort('Exiting, have a great day!', 0);
98
+ return;
99
+ }
100
+
91
101
  await traceStep('tool-setup', () =>
92
102
  startToolSetupFlow(
93
103
  selectedTool,
@@ -114,7 +124,7 @@ You can turn this off by running the wizard with the '--disable-telemetry' flag.
114
124
  }
115
125
 
116
126
  async function askForUsedBundlerTool(): Promise<SupportedTools> {
117
- const selectedTool: SupportedTools | symbol = await abortIfCancelled(
127
+ const selectedTool = await abortIfCancelled(
118
128
  clack.select({
119
129
  message: 'Which framework, bundler or build tool are you using?',
120
130
  options: [
@@ -133,6 +143,11 @@ async function askForUsedBundlerTool(): Promise<SupportedTools> {
133
143
  value: 'nextjs',
134
144
  hint: 'Select this option if you want to set up source maps in a NextJS project.',
135
145
  },
146
+ {
147
+ label: 'Remix',
148
+ value: 'remix',
149
+ hint: 'Select this option if you want to set up source maps in a Remix project.',
150
+ },
136
151
  {
137
152
  label: 'Webpack',
138
153
  value: 'webpack',
@@ -159,10 +174,15 @@ async function askForUsedBundlerTool(): Promise<SupportedTools> {
159
174
  hint: 'Configure source maps when using tsc as build tool',
160
175
  },
161
176
  {
162
- label: 'None of the above',
177
+ label: 'I use another tool',
163
178
  value: 'sentry-cli',
164
179
  hint: 'This will configure source maps upload for you using sentry-cli',
165
180
  },
181
+ {
182
+ label: "I don't minify, transpile or bundle my code",
183
+ value: 'no-tool',
184
+ hint: 'This will exit the wizard',
185
+ },
166
186
  ],
167
187
  initialValue: await detectUsedTool(),
168
188
  }),
@@ -204,6 +224,9 @@ async function startToolSetupFlow(
204
224
  case 'nextjs':
205
225
  await configureNextJsSourceMapsUpload(options, wizardOptions);
206
226
  break;
227
+ case 'remix':
228
+ await configureRemixSourceMapsUpload(options, wizardOptions);
229
+ break;
207
230
  default:
208
231
  await configureSentryCLI(options);
209
232
  break;
@@ -308,8 +331,8 @@ SENTRY_AUTH_TOKEN=${authToken}
308
331
  }
309
332
 
310
333
  function printOutro(url: string, orgSlug: string, projectId: string) {
311
- const pacMan = detectPackageManager() || 'npm';
312
- const buildCommand = `'${pacMan}${pacMan === 'npm' ? ' run' : ''} build'`;
334
+ const packageManager = detectPackageManger();
335
+ const buildCommand = packageManager?.buildCommand ?? 'npm run build';
313
336
 
314
337
  const urlObject = new URL(url);
315
338
  urlObject.host = `${orgSlug}.${urlObject.host}`;
@@ -3,7 +3,7 @@ import * as clack from '@clack/prompts';
3
3
  import chalk from 'chalk';
4
4
  import { runNextjsWizard } from '../../nextjs/nextjs-wizard';
5
5
  import { traceStep } from '../../telemetry';
6
- import { abortIfCancelled, addSentryCliRc } from '../../utils/clack-utils';
6
+ import { abortIfCancelled, addSentryCliConfig } from '../../utils/clack-utils';
7
7
  import { WizardOptions } from '../../utils/types';
8
8
 
9
9
  import { SourceMapUploadToolConfigurationOptions } from './types';
@@ -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
- addSentryCliRc(options.authToken),
102
+ addSentryCliConfig(options.authToken),
103
103
  );
104
104
  }
105
105
 
@@ -0,0 +1,90 @@
1
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
2
+ import * as clack from '@clack/prompts';
3
+ import chalk from 'chalk';
4
+ import { runRemixWizard } from '../../remix/remix-wizard';
5
+ import { traceStep } from '../../telemetry';
6
+ import { abortIfCancelled } from '../../utils/clack-utils';
7
+ import { WizardOptions } from '../../utils/types';
8
+ import { SourceMapUploadToolConfigurationOptions } from './types';
9
+
10
+ import * as Sentry from '@sentry/node';
11
+
12
+ export const configureRemixSourceMapsUpload = async (
13
+ options: SourceMapUploadToolConfigurationOptions,
14
+ wizardOptions: WizardOptions,
15
+ ) => {
16
+ clack.log
17
+ .info(`Source Maps upload for Remix is configured automatically by default if you run the Sentry Wizard for Remix.
18
+ But don't worry, we can redirect you to the wizard now!
19
+ In case you already tried the wizard, we can also show you how to configure your ${chalk.cyan(
20
+ 'remix.config.js',
21
+ )} file manually instead.`);
22
+
23
+ const shouldRedirect: boolean = await abortIfCancelled(
24
+ clack.select({
25
+ message: 'Do you want to run the Sentry Wizard for Remix now?',
26
+ options: [
27
+ {
28
+ label: 'Yes, run the wizard!',
29
+ value: true,
30
+ hint: 'The wizard can also configure your SDK setup',
31
+ },
32
+ {
33
+ label: 'No, show me how to configure it manually',
34
+ value: false,
35
+ },
36
+ ],
37
+ }),
38
+ );
39
+
40
+ Sentry.setTag('redirect-remix-wizard', shouldRedirect);
41
+
42
+ if (shouldRedirect) {
43
+ await traceStep('run-remix-wizard', () => runRemixWizard(wizardOptions));
44
+ clack.intro('Sentry Source Maps Upload Configuration Wizard');
45
+ clack.log.info(
46
+ "Welcome back to the Source Maps wizard - we're almost done ;)",
47
+ );
48
+ } else {
49
+ clack.log.step(
50
+ `Build your app with ${chalk.cyan(
51
+ 'remix build --sourcemap',
52
+ )}, then upload your source maps using ${chalk.cyan(
53
+ 'sentry-upload-sourcemaps',
54
+ )} cli tool.`,
55
+ );
56
+
57
+ clack.log.step(
58
+ `You can add ${chalk.cyan(
59
+ 'sentry-upload-sourcemaps',
60
+ )} to your build script in ${chalk.cyan('package.json')} like this:`,
61
+ );
62
+
63
+ // Intentially logging directly to console here so that the code can be copied/pasted directly
64
+ // eslint-disable-next-line no-console
65
+ console.log(codeSnippet);
66
+
67
+ clack.log.step(`or run it manually after building your app.
68
+
69
+ To see all available options for ${chalk.cyan(
70
+ 'sentry-upload-sourcemaps',
71
+ )}, run ${chalk.cyan('sentry-upload-sourcemaps --help')}
72
+ `);
73
+
74
+ await abortIfCancelled(
75
+ clack.select({
76
+ message: 'Did you finish configuring your build and prod scripts?',
77
+ options: [{ label: 'Yes, continue!', value: true }],
78
+ initialValue: true,
79
+ }),
80
+ );
81
+ }
82
+ };
83
+
84
+ const codeSnippet = chalk.gray(`
85
+ "scripts": {
86
+ ${chalk.greenBright(
87
+ '"build": "remix build --sourcemap && sentry-upload-sourcemaps"',
88
+ )};
89
+ }
90
+ `);