@sentry/wizard 6.6.1 → 6.8.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 (122) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +97 -8
  3. package/dist/bin.js +5 -0
  4. package/dist/bin.js.map +1 -1
  5. package/dist/e2e-tests/tests/help-message.test.js +5 -1
  6. package/dist/e2e-tests/tests/help-message.test.js.map +1 -1
  7. package/dist/e2e-tests/tests/nextjs-15.test.js +79 -0
  8. package/dist/e2e-tests/tests/nextjs-15.test.js.map +1 -1
  9. package/dist/e2e-tests/tests/pnpm-workspace.test.d.ts +1 -0
  10. package/dist/e2e-tests/tests/pnpm-workspace.test.js +206 -0
  11. package/dist/e2e-tests/tests/pnpm-workspace.test.js.map +1 -0
  12. package/dist/e2e-tests/tests/react-router.test.d.ts +1 -0
  13. package/dist/e2e-tests/tests/react-router.test.js +255 -0
  14. package/dist/e2e-tests/tests/react-router.test.js.map +1 -0
  15. package/dist/e2e-tests/utils/index.d.ts +8 -2
  16. package/dist/e2e-tests/utils/index.js +72 -21
  17. package/dist/e2e-tests/utils/index.js.map +1 -1
  18. package/dist/lib/Constants.d.ts +1 -0
  19. package/dist/lib/Constants.js +5 -0
  20. package/dist/lib/Constants.js.map +1 -1
  21. package/dist/src/android/android-wizard.js +8 -1
  22. package/dist/src/android/android-wizard.js.map +1 -1
  23. package/dist/src/angular/angular-wizard.js +8 -1
  24. package/dist/src/angular/angular-wizard.js.map +1 -1
  25. package/dist/src/apple/apple-wizard.js +8 -1
  26. package/dist/src/apple/apple-wizard.js.map +1 -1
  27. package/dist/src/flutter/flutter-wizard.js +8 -1
  28. package/dist/src/flutter/flutter-wizard.js.map +1 -1
  29. package/dist/src/nextjs/nextjs-wizard.js +35 -9
  30. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  31. package/dist/src/nextjs/templates.d.ts +3 -3
  32. package/dist/src/nextjs/templates.js +18 -7
  33. package/dist/src/nextjs/templates.js.map +1 -1
  34. package/dist/src/nuxt/nuxt-wizard.js +8 -1
  35. package/dist/src/nuxt/nuxt-wizard.js.map +1 -1
  36. package/dist/src/react-native/react-native-wizard.js +8 -1
  37. package/dist/src/react-native/react-native-wizard.js.map +1 -1
  38. package/dist/src/react-router/codemods/client.entry.d.ts +1 -0
  39. package/dist/src/react-router/codemods/client.entry.js +73 -0
  40. package/dist/src/react-router/codemods/client.entry.js.map +1 -0
  41. package/dist/src/react-router/codemods/react-router-config.d.ts +9 -0
  42. package/dist/src/react-router/codemods/react-router-config.js +178 -0
  43. package/dist/src/react-router/codemods/react-router-config.js.map +1 -0
  44. package/dist/src/react-router/codemods/root.d.ts +1 -0
  45. package/dist/src/react-router/codemods/root.js +171 -0
  46. package/dist/src/react-router/codemods/root.js.map +1 -0
  47. package/dist/src/react-router/codemods/routes-config.d.ts +1 -0
  48. package/dist/src/react-router/codemods/routes-config.js +106 -0
  49. package/dist/src/react-router/codemods/routes-config.js.map +1 -0
  50. package/dist/src/react-router/codemods/server-entry.d.ts +4 -0
  51. package/dist/src/react-router/codemods/server-entry.js +275 -0
  52. package/dist/src/react-router/codemods/server-entry.js.map +1 -0
  53. package/dist/src/react-router/codemods/utils.d.ts +2 -0
  54. package/dist/src/react-router/codemods/utils.js +13 -0
  55. package/dist/src/react-router/codemods/utils.js.map +1 -0
  56. package/dist/src/react-router/codemods/vite.d.ts +8 -0
  57. package/dist/src/react-router/codemods/vite.js +169 -0
  58. package/dist/src/react-router/codemods/vite.js.map +1 -0
  59. package/dist/src/react-router/react-router-wizard.d.ts +2 -0
  60. package/dist/src/react-router/react-router-wizard.js +254 -0
  61. package/dist/src/react-router/react-router-wizard.js.map +1 -0
  62. package/dist/src/react-router/sdk-example.d.ts +18 -0
  63. package/dist/src/react-router/sdk-example.js +306 -0
  64. package/dist/src/react-router/sdk-example.js.map +1 -0
  65. package/dist/src/react-router/sdk-setup.d.ts +17 -0
  66. package/dist/src/react-router/sdk-setup.js +250 -0
  67. package/dist/src/react-router/sdk-setup.js.map +1 -0
  68. package/dist/src/react-router/templates.d.ts +11 -0
  69. package/dist/src/react-router/templates.js +273 -0
  70. package/dist/src/react-router/templates.js.map +1 -0
  71. package/dist/src/remix/remix-wizard.js +8 -1
  72. package/dist/src/remix/remix-wizard.js.map +1 -1
  73. package/dist/src/run.d.ts +2 -1
  74. package/dist/src/run.js +6 -0
  75. package/dist/src/run.js.map +1 -1
  76. package/dist/src/sourcemaps/sourcemaps-wizard.js +8 -1
  77. package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
  78. package/dist/src/sveltekit/sveltekit-wizard.js +8 -1
  79. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  80. package/dist/src/utils/ast-utils.d.ts +30 -0
  81. package/dist/src/utils/ast-utils.js +71 -1
  82. package/dist/src/utils/ast-utils.js.map +1 -1
  83. package/dist/src/utils/clack/index.d.ts +5 -2
  84. package/dist/src/utils/clack/index.js +8 -0
  85. package/dist/src/utils/clack/index.js.map +1 -1
  86. package/dist/src/utils/package-json.js +86 -2
  87. package/dist/src/utils/package-json.js.map +1 -1
  88. package/dist/src/utils/types.d.ts +9 -0
  89. package/dist/src/utils/types.js.map +1 -1
  90. package/dist/src/version.d.ts +1 -1
  91. package/dist/src/version.js +1 -1
  92. package/dist/src/version.js.map +1 -1
  93. package/dist/test/nextjs/templates.test.js +20 -0
  94. package/dist/test/nextjs/templates.test.js.map +1 -1
  95. package/dist/test/react-router/codemods/client-entry.test.d.ts +1 -0
  96. package/dist/test/react-router/codemods/client-entry.test.js +168 -0
  97. package/dist/test/react-router/codemods/client-entry.test.js.map +1 -0
  98. package/dist/test/react-router/codemods/react-router-config.test.d.ts +1 -0
  99. package/dist/test/react-router/codemods/react-router-config.test.js +168 -0
  100. package/dist/test/react-router/codemods/react-router-config.test.js.map +1 -0
  101. package/dist/test/react-router/codemods/root.test.d.ts +1 -0
  102. package/dist/test/react-router/codemods/root.test.js +178 -0
  103. package/dist/test/react-router/codemods/root.test.js.map +1 -0
  104. package/dist/test/react-router/codemods/server-entry.test.d.ts +1 -0
  105. package/dist/test/react-router/codemods/server-entry.test.js +415 -0
  106. package/dist/test/react-router/codemods/server-entry.test.js.map +1 -0
  107. package/dist/test/react-router/codemods/vite.test.d.ts +1 -0
  108. package/dist/test/react-router/codemods/vite.test.js +158 -0
  109. package/dist/test/react-router/codemods/vite.test.js.map +1 -0
  110. package/dist/test/react-router/routes-config.test.d.ts +1 -0
  111. package/dist/test/react-router/routes-config.test.js +156 -0
  112. package/dist/test/react-router/routes-config.test.js.map +1 -0
  113. package/dist/test/react-router/sdk-setup.test.d.ts +1 -0
  114. package/dist/test/react-router/sdk-setup.test.js +411 -0
  115. package/dist/test/react-router/sdk-setup.test.js.map +1 -0
  116. package/dist/test/react-router/templates.test.d.ts +1 -0
  117. package/dist/test/react-router/templates.test.js +220 -0
  118. package/dist/test/react-router/templates.test.js.map +1 -0
  119. package/dist/test/utils/package-json.test.d.ts +1 -0
  120. package/dist/test/utils/package-json.test.js +428 -0
  121. package/dist/test/utils/package-json.test.js.map +1 -0
  122. package/package.json +4 -2
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runReactRouterWizard = void 0;
7
+ // @ts-expect-error - clack is ESM and TS complains about that. It works though
8
+ const prompts_1 = __importDefault(require("@clack/prompts"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const telemetry_1 = require("../telemetry");
11
+ const clack_1 = require("../utils/clack");
12
+ const mcp_config_1 = require("../utils/clack/mcp-config");
13
+ const package_json_1 = require("../utils/package-json");
14
+ const debug_1 = require("../utils/debug");
15
+ const sdk_example_1 = require("./sdk-example");
16
+ const sdk_setup_1 = require("./sdk-setup");
17
+ const templates_1 = require("./templates");
18
+ async function runReactRouterWizard(options) {
19
+ return (0, telemetry_1.withTelemetry)({
20
+ enabled: options.telemetryEnabled,
21
+ integration: 'reactRouter',
22
+ wizardOptions: options,
23
+ }, () => runReactRouterWizardWithTelemetry(options));
24
+ }
25
+ exports.runReactRouterWizard = runReactRouterWizard;
26
+ async function runReactRouterWizardWithTelemetry(options) {
27
+ (0, clack_1.printWelcome)({
28
+ wizardName: 'Sentry React Router Wizard',
29
+ promoCode: options.promoCode,
30
+ });
31
+ const packageJson = await (0, clack_1.getPackageDotJson)();
32
+ if (!packageJson) {
33
+ prompts_1.default.log.error('Could not find a package.json file in the current directory');
34
+ return;
35
+ }
36
+ const typeScriptDetected = (0, clack_1.isUsingTypeScript)();
37
+ if (!(0, sdk_setup_1.isReactRouterV7)(packageJson)) {
38
+ prompts_1.default.log.error('This wizard requires React Router v7. Please upgrade your React Router version to v7.0.0 or higher.\n\nFor upgrade instructions, visit: https://react-router.dev/upgrade/v7');
39
+ return;
40
+ }
41
+ await (0, clack_1.confirmContinueIfNoOrDirtyGitRepo)({
42
+ ignoreGitChanges: options.ignoreGitChanges,
43
+ cwd: undefined,
44
+ });
45
+ const sentryAlreadyInstalled = (0, package_json_1.hasPackageInstalled)('@sentry/react-router', packageJson);
46
+ const projectData = await (0, clack_1.getOrAskForProjectData)(options, 'javascript-react-router');
47
+ if (projectData.spotlight) {
48
+ prompts_1.default.log.warn('Spotlight mode is not yet supported for React Router.');
49
+ prompts_1.default.log.info('Spotlight is currently only available for Next.js.');
50
+ await (0, clack_1.abort)('Exiting wizard', 0);
51
+ return;
52
+ }
53
+ const { selectedProject, authToken, selfHosted, sentryUrl } = projectData;
54
+ await (0, clack_1.installPackage)({
55
+ packageName: '@sentry/react-router',
56
+ alreadyInstalled: sentryAlreadyInstalled,
57
+ });
58
+ const featureSelection = await (0, clack_1.featureSelectionPrompt)([
59
+ {
60
+ id: 'performance',
61
+ prompt: `Do you want to enable ${chalk_1.default.bold('Tracing')} to track the performance of your application?`,
62
+ enabledHint: 'recommended',
63
+ },
64
+ {
65
+ id: 'replay',
66
+ prompt: `Do you want to enable ${chalk_1.default.bold('Session Replay')} to get a video-like reproduction of errors during a user session?`,
67
+ enabledHint: 'recommended, but increases bundle size',
68
+ },
69
+ {
70
+ id: 'logs',
71
+ prompt: `Do you want to enable ${chalk_1.default.bold('Logs')} to send your application logs to Sentry?`,
72
+ enabledHint: 'recommended',
73
+ },
74
+ {
75
+ id: 'profiling',
76
+ prompt: `Do you want to enable ${chalk_1.default.bold('Profiling')} to track application performance in detail?`,
77
+ enabledHint: 'recommended for production debugging',
78
+ },
79
+ ]);
80
+ if (featureSelection.profiling) {
81
+ const profilingAlreadyInstalled = (0, package_json_1.hasPackageInstalled)('@sentry/profiling-node', packageJson);
82
+ await (0, clack_1.installPackage)({
83
+ packageName: '@sentry/profiling-node',
84
+ alreadyInstalled: profilingAlreadyInstalled,
85
+ });
86
+ }
87
+ const createExamplePageSelection = await (0, clack_1.askShouldCreateExamplePage)();
88
+ (0, telemetry_1.traceStep)('Reveal missing entry files', () => {
89
+ try {
90
+ (0, sdk_setup_1.runReactRouterReveal)(typeScriptDetected);
91
+ prompts_1.default.log.success('Entry files are ready for instrumentation');
92
+ }
93
+ catch (e) {
94
+ prompts_1.default.log.warn(`Could not run 'npx react-router reveal'.
95
+ Please create your entry files manually using React Router v7 commands.`);
96
+ (0, debug_1.debug)(e);
97
+ }
98
+ });
99
+ await (0, telemetry_1.traceStep)('Initialize Sentry on client entry', async () => {
100
+ try {
101
+ await (0, sdk_setup_1.initializeSentryOnEntryClient)(selectedProject.keys[0].dsn.public, featureSelection.performance, featureSelection.replay, featureSelection.logs, typeScriptDetected);
102
+ }
103
+ catch (e) {
104
+ prompts_1.default.log.warn(`Could not initialize Sentry on client entry automatically.`);
105
+ const clientEntryFilename = `entry.client.${typeScriptDetected ? 'tsx' : 'jsx'}`;
106
+ const manualClientContent = (0, templates_1.getManualClientEntryContent)(selectedProject.keys[0].dsn.public, featureSelection.performance, featureSelection.replay, featureSelection.logs);
107
+ await (0, clack_1.showCopyPasteInstructions)({
108
+ filename: clientEntryFilename,
109
+ codeSnippet: manualClientContent,
110
+ hint: 'This enables error tracking and performance monitoring for your React Router app',
111
+ });
112
+ (0, debug_1.debug)(e);
113
+ }
114
+ });
115
+ await (0, telemetry_1.traceStep)('Instrument root route', async () => {
116
+ try {
117
+ await (0, sdk_setup_1.instrumentRootRoute)(typeScriptDetected);
118
+ }
119
+ catch (e) {
120
+ prompts_1.default.log.warn(`Could not instrument root route automatically.`);
121
+ const rootFilename = `app/root.${typeScriptDetected ? 'tsx' : 'jsx'}`;
122
+ const manualRootContent = (0, templates_1.getManualRootContent)(typeScriptDetected);
123
+ await (0, clack_1.showCopyPasteInstructions)({
124
+ filename: rootFilename,
125
+ codeSnippet: manualRootContent,
126
+ hint: 'This adds error boundary integration to capture exceptions in your React Router app',
127
+ });
128
+ (0, debug_1.debug)(e);
129
+ }
130
+ });
131
+ await (0, telemetry_1.traceStep)('Instrument server entry', async () => {
132
+ try {
133
+ await (0, sdk_setup_1.instrumentSentryOnEntryServer)(typeScriptDetected);
134
+ }
135
+ catch (e) {
136
+ prompts_1.default.log.warn(`Could not initialize Sentry on server entry automatically.`);
137
+ const serverEntryFilename = `entry.server.${typeScriptDetected ? 'tsx' : 'jsx'}`;
138
+ const manualServerContent = (0, templates_1.getManualServerEntryContent)();
139
+ await (0, clack_1.showCopyPasteInstructions)({
140
+ filename: serverEntryFilename,
141
+ codeSnippet: manualServerContent,
142
+ hint: 'This configures server-side request handling and error tracking',
143
+ });
144
+ (0, debug_1.debug)(e);
145
+ }
146
+ });
147
+ await (0, telemetry_1.traceStep)('Create server instrumentation file', async () => {
148
+ try {
149
+ (0, sdk_setup_1.createServerInstrumentationFile)(selectedProject.keys[0].dsn.public, {
150
+ performance: featureSelection.performance,
151
+ replay: featureSelection.replay,
152
+ logs: featureSelection.logs,
153
+ profiling: featureSelection.profiling,
154
+ });
155
+ }
156
+ catch (e) {
157
+ prompts_1.default.log.warn('Could not create a server instrumentation file automatically.');
158
+ const manualServerInstrumentContent = (0, templates_1.getManualServerInstrumentContent)(selectedProject.keys[0].dsn.public, featureSelection.performance, featureSelection.profiling, featureSelection.logs);
159
+ await (0, clack_1.showCopyPasteInstructions)({
160
+ filename: 'instrument.server.mjs',
161
+ codeSnippet: manualServerInstrumentContent,
162
+ hint: 'Create the file if it does not exist - this initializes Sentry before your application starts',
163
+ });
164
+ (0, debug_1.debug)(e);
165
+ }
166
+ });
167
+ await (0, telemetry_1.traceStep)('Update package.json scripts', async () => {
168
+ try {
169
+ await (0, sdk_setup_1.updatePackageJsonScripts)();
170
+ }
171
+ catch (e) {
172
+ prompts_1.default.log.warn('Could not update start script automatically.');
173
+ await (0, clack_1.showCopyPasteInstructions)({
174
+ filename: 'package.json',
175
+ codeSnippet: (0, clack_1.makeCodeSnippet)(true, (unchanged, plus, minus) => {
176
+ return unchanged(`{
177
+ scripts: {
178
+ ${minus('"start": "react-router dev"')}
179
+ ${plus('"start": "NODE_OPTIONS=\'--import ./instrument.server.mjs\' react-router-serve ./build/server/index.js"')}
180
+ ${minus('"dev": "react-router dev"')}
181
+ ${plus('"dev": "NODE_OPTIONS=\'--import ./instrument.server.mjs\' react-router dev"')}
182
+ },
183
+ // ... rest of your package.json
184
+ }`);
185
+ }),
186
+ });
187
+ (0, debug_1.debug)(e);
188
+ }
189
+ });
190
+ await (0, telemetry_1.traceStep)('Create build plugin env file', async () => {
191
+ try {
192
+ await (0, clack_1.addDotEnvSentryBuildPluginFile)(authToken);
193
+ }
194
+ catch (e) {
195
+ prompts_1.default.log.warn('Could not create .env.sentry-build-plugin file. Please create it manually.');
196
+ (0, debug_1.debug)(e);
197
+ }
198
+ });
199
+ // Validate auth token before configuring sourcemap uploads
200
+ if (!authToken) {
201
+ prompts_1.default.log.warn(`${chalk_1.default.yellow('Warning:')} No auth token found. Sourcemap uploads will not work without ${chalk_1.default.cyan('SENTRY_AUTH_TOKEN')}.\n` +
202
+ `Please set ${chalk_1.default.cyan('SENTRY_AUTH_TOKEN')} in your ${chalk_1.default.cyan('.env.sentry-build-plugin')} file or environment variables.`);
203
+ }
204
+ // Configure Vite plugin for sourcemap uploads
205
+ await (0, telemetry_1.traceStep)('Configure Vite plugin for sourcemap uploads', async () => {
206
+ try {
207
+ await (0, sdk_setup_1.configureReactRouterVitePlugin)(selectedProject.organization.slug, selectedProject.slug);
208
+ }
209
+ catch (e) {
210
+ prompts_1.default.log.warn(`Could not configure Vite plugin for sourcemap uploads automatically.`);
211
+ await (0, clack_1.showCopyPasteInstructions)({
212
+ filename: `vite.config.${typeScriptDetected ? 'ts' : 'js'}`,
213
+ codeSnippet: (0, templates_1.getManualViteConfigContent)(selectedProject.organization.slug, selectedProject.slug),
214
+ hint: 'This enables automatic sourcemap uploads during build for better error tracking',
215
+ });
216
+ (0, debug_1.debug)(e);
217
+ }
218
+ });
219
+ // Configure React Router config for build hook
220
+ await (0, telemetry_1.traceStep)('Configure React Router build hook', async () => {
221
+ try {
222
+ await (0, sdk_setup_1.configureReactRouterConfig)(typeScriptDetected);
223
+ }
224
+ catch (e) {
225
+ prompts_1.default.log.warn(`Could not configure React Router build hook automatically.`);
226
+ await (0, clack_1.showCopyPasteInstructions)({
227
+ filename: `react-router.config.${typeScriptDetected ? 'ts' : 'js'}`,
228
+ codeSnippet: (0, templates_1.getManualReactRouterConfigContent)(typeScriptDetected),
229
+ hint: 'This enables automatic sourcemap uploads at the end of the build process',
230
+ });
231
+ (0, debug_1.debug)(e);
232
+ }
233
+ });
234
+ // Create example page if requested
235
+ if (createExamplePageSelection) {
236
+ await (0, telemetry_1.traceStep)('Create example page', async () => {
237
+ await (0, sdk_example_1.createExamplePage)({
238
+ selfHosted,
239
+ orgSlug: selectedProject.organization.slug,
240
+ projectId: selectedProject.id,
241
+ url: sentryUrl,
242
+ isTS: typeScriptDetected,
243
+ projectDir: process.cwd(),
244
+ });
245
+ });
246
+ }
247
+ await (0, clack_1.runPrettierIfInstalled)({ cwd: undefined });
248
+ // Offer optional project-scoped MCP config for Sentry with org and project scope
249
+ await (0, mcp_config_1.offerProjectScopedMcpConfig)(selectedProject.organization.slug, selectedProject.slug);
250
+ prompts_1.default.outro(`${chalk_1.default.green('Successfully installed the Sentry React Router SDK!')}${createExamplePageSelection
251
+ ? `\n\nYou can validate your setup by visiting ${chalk_1.default.cyan('"/sentry-example-page"')} in your application.`
252
+ : ''}`);
253
+ }
254
+ //# sourceMappingURL=react-router-wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-router-wizard.js","sourceRoot":"","sources":["../../../src/react-router/react-router-wizard.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-expect-error - clack is ESM and TS complains about that. It works though\nimport clack from '@clack/prompts';\nimport chalk from 'chalk';\n\nimport type { WizardOptions } from '../utils/types';\nimport { withTelemetry, traceStep } from '../telemetry';\nimport {\n abort,\n askShouldCreateExamplePage,\n confirmContinueIfNoOrDirtyGitRepo,\n featureSelectionPrompt,\n getOrAskForProjectData,\n getPackageDotJson,\n isUsingTypeScript,\n printWelcome,\n installPackage,\n addDotEnvSentryBuildPluginFile,\n showCopyPasteInstructions,\n makeCodeSnippet,\n runPrettierIfInstalled,\n} from '../utils/clack';\nimport { offerProjectScopedMcpConfig } from '../utils/clack/mcp-config';\nimport { hasPackageInstalled } from '../utils/package-json';\nimport { debug } from '../utils/debug';\nimport { createExamplePage } from './sdk-example';\nimport {\n isReactRouterV7,\n runReactRouterReveal,\n initializeSentryOnEntryClient,\n instrumentRootRoute,\n createServerInstrumentationFile,\n updatePackageJsonScripts,\n instrumentSentryOnEntryServer,\n configureReactRouterConfig,\n configureReactRouterVitePlugin,\n} from './sdk-setup';\nimport {\n getManualClientEntryContent,\n getManualServerEntryContent,\n getManualRootContent,\n getManualServerInstrumentContent,\n getManualReactRouterConfigContent,\n getManualViteConfigContent,\n} from './templates';\n\nexport async function runReactRouterWizard(\n options: WizardOptions,\n): Promise<void> {\n return withTelemetry(\n {\n enabled: options.telemetryEnabled,\n integration: 'reactRouter',\n wizardOptions: options,\n },\n () => runReactRouterWizardWithTelemetry(options),\n );\n}\n\nasync function runReactRouterWizardWithTelemetry(\n options: WizardOptions,\n): Promise<void> {\n printWelcome({\n wizardName: 'Sentry React Router Wizard',\n promoCode: options.promoCode,\n });\n\n const packageJson = await getPackageDotJson();\n\n if (!packageJson) {\n clack.log.error(\n 'Could not find a package.json file in the current directory',\n );\n return;\n }\n\n const typeScriptDetected = isUsingTypeScript();\n\n if (!isReactRouterV7(packageJson)) {\n clack.log.error(\n 'This wizard requires React Router v7. Please upgrade your React Router version to v7.0.0 or higher.\\n\\nFor upgrade instructions, visit: https://react-router.dev/upgrade/v7',\n );\n return;\n }\n\n await confirmContinueIfNoOrDirtyGitRepo({\n ignoreGitChanges: options.ignoreGitChanges,\n cwd: undefined,\n });\n\n const sentryAlreadyInstalled = hasPackageInstalled(\n '@sentry/react-router',\n packageJson,\n );\n\n const projectData = await getOrAskForProjectData(\n options,\n 'javascript-react-router',\n );\n\n if (projectData.spotlight) {\n clack.log.warn('Spotlight mode is not yet supported for React Router.');\n clack.log.info('Spotlight is currently only available for Next.js.');\n await abort('Exiting wizard', 0);\n return;\n }\n\n const { selectedProject, authToken, selfHosted, sentryUrl } = projectData;\n\n await installPackage({\n packageName: '@sentry/react-router',\n alreadyInstalled: sentryAlreadyInstalled,\n });\n\n const featureSelection = await featureSelectionPrompt([\n {\n id: 'performance',\n prompt: `Do you want to enable ${chalk.bold(\n 'Tracing',\n )} to track the performance of your application?`,\n enabledHint: 'recommended',\n },\n {\n id: 'replay',\n prompt: `Do you want to enable ${chalk.bold(\n 'Session Replay',\n )} to get a video-like reproduction of errors during a user session?`,\n enabledHint: 'recommended, but increases bundle size',\n },\n {\n id: 'logs',\n prompt: `Do you want to enable ${chalk.bold(\n 'Logs',\n )} to send your application logs to Sentry?`,\n enabledHint: 'recommended',\n },\n {\n id: 'profiling',\n prompt: `Do you want to enable ${chalk.bold(\n 'Profiling',\n )} to track application performance in detail?`,\n enabledHint: 'recommended for production debugging',\n },\n ]);\n\n if (featureSelection.profiling) {\n const profilingAlreadyInstalled = hasPackageInstalled(\n '@sentry/profiling-node',\n packageJson,\n );\n\n await installPackage({\n packageName: '@sentry/profiling-node',\n alreadyInstalled: profilingAlreadyInstalled,\n });\n }\n\n const createExamplePageSelection = await askShouldCreateExamplePage();\n\n traceStep('Reveal missing entry files', () => {\n try {\n runReactRouterReveal(typeScriptDetected);\n clack.log.success('Entry files are ready for instrumentation');\n } catch (e) {\n clack.log.warn(`Could not run 'npx react-router reveal'.\nPlease create your entry files manually using React Router v7 commands.`);\n debug(e);\n }\n });\n\n await traceStep('Initialize Sentry on client entry', async () => {\n try {\n await initializeSentryOnEntryClient(\n selectedProject.keys[0].dsn.public,\n featureSelection.performance,\n featureSelection.replay,\n featureSelection.logs,\n typeScriptDetected,\n );\n } catch (e) {\n clack.log.warn(\n `Could not initialize Sentry on client entry automatically.`,\n );\n\n const clientEntryFilename = `entry.client.${\n typeScriptDetected ? 'tsx' : 'jsx'\n }`;\n\n const manualClientContent = getManualClientEntryContent(\n selectedProject.keys[0].dsn.public,\n featureSelection.performance,\n featureSelection.replay,\n featureSelection.logs,\n );\n\n await showCopyPasteInstructions({\n filename: clientEntryFilename,\n codeSnippet: manualClientContent,\n hint: 'This enables error tracking and performance monitoring for your React Router app',\n });\n\n debug(e);\n }\n });\n\n await traceStep('Instrument root route', async () => {\n try {\n await instrumentRootRoute(typeScriptDetected);\n } catch (e) {\n clack.log.warn(`Could not instrument root route automatically.`);\n\n const rootFilename = `app/root.${typeScriptDetected ? 'tsx' : 'jsx'}`;\n const manualRootContent = getManualRootContent(typeScriptDetected);\n\n await showCopyPasteInstructions({\n filename: rootFilename,\n codeSnippet: manualRootContent,\n hint: 'This adds error boundary integration to capture exceptions in your React Router app',\n });\n\n debug(e);\n }\n });\n\n await traceStep('Instrument server entry', async () => {\n try {\n await instrumentSentryOnEntryServer(typeScriptDetected);\n } catch (e) {\n clack.log.warn(\n `Could not initialize Sentry on server entry automatically.`,\n );\n\n const serverEntryFilename = `entry.server.${\n typeScriptDetected ? 'tsx' : 'jsx'\n }`;\n const manualServerContent = getManualServerEntryContent();\n\n await showCopyPasteInstructions({\n filename: serverEntryFilename,\n codeSnippet: manualServerContent,\n hint: 'This configures server-side request handling and error tracking',\n });\n\n debug(e);\n }\n });\n\n await traceStep('Create server instrumentation file', async () => {\n try {\n createServerInstrumentationFile(selectedProject.keys[0].dsn.public, {\n performance: featureSelection.performance,\n replay: featureSelection.replay,\n logs: featureSelection.logs,\n profiling: featureSelection.profiling,\n });\n } catch (e) {\n clack.log.warn(\n 'Could not create a server instrumentation file automatically.',\n );\n\n const manualServerInstrumentContent = getManualServerInstrumentContent(\n selectedProject.keys[0].dsn.public,\n featureSelection.performance,\n featureSelection.profiling,\n featureSelection.logs,\n );\n\n await showCopyPasteInstructions({\n filename: 'instrument.server.mjs',\n codeSnippet: manualServerInstrumentContent,\n hint: 'Create the file if it does not exist - this initializes Sentry before your application starts',\n });\n\n debug(e);\n }\n });\n\n await traceStep('Update package.json scripts', async () => {\n try {\n await updatePackageJsonScripts();\n } catch (e) {\n clack.log.warn('Could not update start script automatically.');\n\n await showCopyPasteInstructions({\n filename: 'package.json',\n codeSnippet: makeCodeSnippet(true, (unchanged, plus, minus) => {\n return unchanged(`{\n scripts: {\n ${minus('\"start\": \"react-router dev\"')}\n ${plus(\n '\"start\": \"NODE_OPTIONS=\\'--import ./instrument.server.mjs\\' react-router-serve ./build/server/index.js\"',\n )}\n ${minus('\"dev\": \"react-router dev\"')}\n ${plus(\n '\"dev\": \"NODE_OPTIONS=\\'--import ./instrument.server.mjs\\' react-router dev\"',\n )}\n },\n // ... rest of your package.json\n }`);\n }),\n });\n\n debug(e);\n }\n });\n\n await traceStep('Create build plugin env file', async () => {\n try {\n await addDotEnvSentryBuildPluginFile(authToken);\n } catch (e) {\n clack.log.warn(\n 'Could not create .env.sentry-build-plugin file. Please create it manually.',\n );\n debug(e);\n }\n });\n\n // Validate auth token before configuring sourcemap uploads\n if (!authToken) {\n clack.log.warn(\n `${chalk.yellow(\n 'Warning:',\n )} No auth token found. Sourcemap uploads will not work without ${chalk.cyan(\n 'SENTRY_AUTH_TOKEN',\n )}.\\n` +\n `Please set ${chalk.cyan('SENTRY_AUTH_TOKEN')} in your ${chalk.cyan(\n '.env.sentry-build-plugin',\n )} file or environment variables.`,\n );\n }\n\n // Configure Vite plugin for sourcemap uploads\n await traceStep('Configure Vite plugin for sourcemap uploads', async () => {\n try {\n await configureReactRouterVitePlugin(\n selectedProject.organization.slug,\n selectedProject.slug,\n );\n } catch (e) {\n clack.log.warn(\n `Could not configure Vite plugin for sourcemap uploads automatically.`,\n );\n\n await showCopyPasteInstructions({\n filename: `vite.config.${typeScriptDetected ? 'ts' : 'js'}`,\n codeSnippet: getManualViteConfigContent(\n selectedProject.organization.slug,\n selectedProject.slug,\n ),\n hint: 'This enables automatic sourcemap uploads during build for better error tracking',\n });\n\n debug(e);\n }\n });\n\n // Configure React Router config for build hook\n await traceStep('Configure React Router build hook', async () => {\n try {\n await configureReactRouterConfig(typeScriptDetected);\n } catch (e) {\n clack.log.warn(\n `Could not configure React Router build hook automatically.`,\n );\n\n await showCopyPasteInstructions({\n filename: `react-router.config.${typeScriptDetected ? 'ts' : 'js'}`,\n codeSnippet: getManualReactRouterConfigContent(typeScriptDetected),\n hint: 'This enables automatic sourcemap uploads at the end of the build process',\n });\n\n debug(e);\n }\n });\n\n // Create example page if requested\n if (createExamplePageSelection) {\n await traceStep('Create example page', async () => {\n await createExamplePage({\n selfHosted,\n orgSlug: selectedProject.organization.slug,\n projectId: selectedProject.id,\n url: sentryUrl,\n isTS: typeScriptDetected,\n projectDir: process.cwd(),\n });\n });\n }\n\n await runPrettierIfInstalled({ cwd: undefined });\n\n // Offer optional project-scoped MCP config for Sentry with org and project scope\n await offerProjectScopedMcpConfig(\n selectedProject.organization.slug,\n selectedProject.slug,\n );\n\n clack.outro(\n `${chalk.green('Successfully installed the Sentry React Router SDK!')}${\n createExamplePageSelection\n ? `\\n\\nYou can validate your setup by visiting ${chalk.cyan(\n '\"/sentry-example-page\"',\n )} in your application.`\n : ''\n }`,\n );\n}\n"]}
@@ -0,0 +1,18 @@
1
+ export declare function createExamplePage(options: {
2
+ selfHosted: boolean;
3
+ orgSlug: string;
4
+ projectId: string;
5
+ url: string;
6
+ isTS: boolean;
7
+ projectDir: string;
8
+ }): Promise<void>;
9
+ export declare function getSentryExamplePageContents(options: {
10
+ selfHosted: boolean;
11
+ orgSlug: string;
12
+ projectId: string;
13
+ url: string;
14
+ isTS?: boolean;
15
+ }): string;
16
+ export declare function getSentryExampleApiContents(options: {
17
+ isTS?: boolean;
18
+ }): string;
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.getSentryExampleApiContents = exports.getSentryExamplePageContents = exports.createExamplePage = void 0;
30
+ const fs = __importStar(require("fs"));
31
+ const path = __importStar(require("path"));
32
+ // @ts-expect-error - clack is ESM and TS complains about that. It works though
33
+ const prompts_1 = __importDefault(require("@clack/prompts"));
34
+ const routes_config_1 = require("./codemods/routes-config");
35
+ const sdk_setup_1 = require("./sdk-setup");
36
+ async function createExamplePage(options) {
37
+ const routesPath = path.join(options.projectDir, 'app', 'routes');
38
+ if (!fs.existsSync(routesPath)) {
39
+ fs.mkdirSync(routesPath, { recursive: true });
40
+ }
41
+ const exampleRoutePath = (0, sdk_setup_1.getRouteFilePath)('sentry-example-page', options.isTS);
42
+ if (fs.existsSync(exampleRoutePath)) {
43
+ prompts_1.default.log.warn(`It seems like a sentry example page already exists (${path.basename(exampleRoutePath)}). Skipping creation of example route.`);
44
+ return;
45
+ }
46
+ await fs.promises.writeFile(exampleRoutePath, getSentryExamplePageContents(options));
47
+ // Create the API route for backend error testing
48
+ const apiRoutePath = path.join(routesPath, `api.sentry-example-api.${options.isTS ? 'ts' : 'js'}`);
49
+ if (!fs.existsSync(apiRoutePath)) {
50
+ await fs.promises.writeFile(apiRoutePath, getSentryExampleApiContents(options));
51
+ prompts_1.default.log.info(`Created sentry example API route at ${apiRoutePath}.`);
52
+ }
53
+ // Check if there's a routes.ts configuration file and add the route using codemod
54
+ const routesConfigPath = path.join(options.projectDir, 'app', 'routes.ts');
55
+ if (fs.existsSync(routesConfigPath)) {
56
+ try {
57
+ await (0, routes_config_1.addRoutesToConfig)(routesConfigPath, options.isTS);
58
+ }
59
+ catch (e) {
60
+ prompts_1.default.log.warn(`Could not update routes.ts configuration: ${e instanceof Error ? e.message : String(e)}`);
61
+ prompts_1.default.log.info('Please manually add these routes to your routes.ts file: route("/sentry-example-page", "routes/sentry-example-page.tsx") and route("/api/sentry-example-api", "routes/api.sentry-example-api.ts")');
62
+ }
63
+ }
64
+ prompts_1.default.log.info(`Created sentry example page at ${exampleRoutePath}.`);
65
+ }
66
+ exports.createExamplePage = createExamplePage;
67
+ function getSentryExamplePageContents(options) {
68
+ const issuesPageLink = options.selfHosted
69
+ ? `${options.url}organizations/${options.orgSlug}/issues/?project=${options.projectId}`
70
+ : `https://${options.orgSlug}.sentry.io/issues/?project=${options.projectId}`;
71
+ return `import * as Sentry from '@sentry/react-router';
72
+ import { useState, useEffect } from 'react';
73
+
74
+ class SentryExampleFrontendError extends Error {
75
+ constructor(message${options.isTS ? ': string | undefined' : ''}) {
76
+ super(message);
77
+ this.name = "SentryExampleFrontendError";
78
+ }
79
+ }
80
+
81
+ export const meta = () => {
82
+ return [
83
+ { title: "sentry-example-page" },
84
+ ];
85
+ }
86
+
87
+ export default function SentryExamplePage() {
88
+ const [hasSentError, setHasSentError] = useState(false);
89
+ const [isConnected, setIsConnected] = useState(true);
90
+
91
+ useEffect(() => {
92
+ async function checkConnectivity() {
93
+ const result = await Sentry.diagnoseSdkConnectivity();
94
+ setIsConnected(result !== 'sentry-unreachable');
95
+ }
96
+ checkConnectivity();
97
+ }, [setIsConnected]);
98
+
99
+ return (
100
+ <div>
101
+ <main>
102
+ <div className="flex-spacer" />
103
+ <svg height="40" width="40" fill="none" xmlns="http://www.w3.org/2000/svg">
104
+ <path d="M21.85 2.995a3.698 3.698 0 0 1 1.353 1.354l16.303 28.278a3.703 3.703 0 0 1-1.354 5.053 3.694 3.694 0 0 1-1.848.496h-3.828a31.149 31.149 0 0 0 0-3.09h3.815a.61.61 0 0 0 .537-.917L20.523 5.893a.61.61 0 0 0-1.057 0l-3.739 6.494a28.948 28.948 0 0 1 9.63 10.453 28.988 28.988 0 0 1 3.499 13.78v1.542h-9.852v-1.544a19.106 19.106 0 0 0-2.182-8.85 19.08 19.08 0 0 0-6.032-6.829l-1.85 3.208a15.377 15.377 0 0 1 6.382 12.484v1.542H3.696A3.694 3.694 0 0 1 0 34.473c0-.648.17-1.286.494-1.849l2.33-4.074a8.562 8.562 0 0 1 2.689 1.536L3.158 34.17a.611.611 0 0 0 .538.917h8.448a12.481 12.481 0 0 0-6.037-9.09l-1.344-.772 4.908-8.545 1.344.77a22.16 22.16 0 0 1 7.705 7.444 22.193 22.193 0 0 1 3.316 10.193h3.699a25.892 25.892 0 0 0-3.811-12.033 25.856 25.856 0 0 0-9.046-8.796l-1.344-.772 5.269-9.136a3.698 3.698 0 0 1 3.2-1.849c.648 0 1.285.17 1.847.495Z" fill="currentcolor"/>
105
+ </svg>
106
+ <h1>
107
+ sentry-example-page
108
+ </h1>
109
+
110
+ <p className="description">
111
+ Click the button below, and view the sample error on the Sentry <a target="_blank" rel="noreferrer" href="${issuesPageLink}">Issues Page</a>.
112
+ For more details about setting up Sentry, <a target="_blank" rel="noreferrer" href="https://docs.sentry.io/platforms/javascript/guides/react-router/">read our docs</a>.
113
+ </p>
114
+
115
+ <button
116
+ type="button"
117
+ onClick={async () => {
118
+ await Sentry.startSpan({
119
+ name: 'Example Frontend Span',
120
+ op: 'test'
121
+ }, async () => {
122
+ const res = await fetch("/api/sentry-example-api");
123
+ if (!res.ok) {
124
+ setHasSentError(true);
125
+ }
126
+ });
127
+ throw new SentryExampleFrontendError("This error is raised on the frontend of the example page.");
128
+ }}
129
+ disabled={!isConnected}
130
+ >
131
+ <span>
132
+ Throw Sample Error
133
+ </span>
134
+ </button>
135
+
136
+ {hasSentError ? (
137
+ <p className="success">
138
+ Sample error was sent to Sentry.
139
+ </p>
140
+ ) : !isConnected ? (
141
+ <div className="connectivity-error">
142
+ <p>It looks like network requests to Sentry are being blocked, which will prevent errors from being captured. Try disabling your ad-blocker to complete the test.</p>
143
+ </div>
144
+ ) : (
145
+ <div className="success_placeholder" />
146
+ )}
147
+
148
+ <div className="flex-spacer" />
149
+ </main>
150
+
151
+ {/* Not for production use! We're just saving you from having to delete an extra CSS file ;) */}
152
+ <style dangerouslySetInnerHTML={{ __html: styles }}></style>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ const styles = \`
158
+ main {
159
+ display: flex;
160
+ min-height: 100vh;
161
+ flex-direction: column;
162
+ justify-content: center;
163
+ align-items: center;
164
+ gap: 16px;
165
+ padding: 16px;
166
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
167
+ }
168
+
169
+ h1 {
170
+ padding: 0px 4px;
171
+ border-radius: 4px;
172
+ background-color: rgba(24, 20, 35, 0.03);
173
+ font-family: monospace;
174
+ font-size: 20px;
175
+ line-height: 1.2;
176
+ }
177
+
178
+ p {
179
+ margin: 0;
180
+ font-size: 20px;
181
+ }
182
+
183
+ a {
184
+ color: #6341F0;
185
+ text-decoration: underline;
186
+ cursor: pointer;
187
+
188
+ @media (prefers-color-scheme: dark) {
189
+ color: #B3A1FF;
190
+ }
191
+ }
192
+
193
+ button {
194
+ border-radius: 8px;
195
+ color: white;
196
+ cursor: pointer;
197
+ background-color: #553DB8;
198
+ border: none;
199
+ padding: 0;
200
+ margin-top: 4px;
201
+
202
+ & > span {
203
+ display: inline-block;
204
+ padding: 12px 16px;
205
+ border-radius: inherit;
206
+ font-size: 20px;
207
+ font-weight: bold;
208
+ line-height: 1;
209
+ background-color: #7553FF;
210
+ border: 1px solid #553DB8;
211
+ transform: translateY(-4px);
212
+ }
213
+
214
+ &:hover > span {
215
+ transform: translateY(-8px);
216
+ }
217
+
218
+ &:active > span {
219
+ transform: translateY(0);
220
+ }
221
+
222
+ &:disabled {
223
+ cursor: not-allowed;
224
+ opacity: 0.6;
225
+
226
+ & > span {
227
+ transform: translateY(0);
228
+ border: none;
229
+ }
230
+ }
231
+ }
232
+
233
+ .description {
234
+ text-align: center;
235
+ color: #6E6C75;
236
+ max-width: 500px;
237
+ line-height: 1.5;
238
+ font-size: 20px;
239
+
240
+ @media (prefers-color-scheme: dark) {
241
+ color: #A49FB5;
242
+ }
243
+ }
244
+
245
+ .flex-spacer {
246
+ flex: 1;
247
+ }
248
+
249
+ .success {
250
+ padding: 12px 16px;
251
+ border-radius: 8px;
252
+ font-size: 20px;
253
+ line-height: 1;
254
+ background-color: #00F261;
255
+ border: 1px solid #00BF4D;
256
+ color: #181423;
257
+ }
258
+
259
+ .success_placeholder {
260
+ height: 46px;
261
+ }
262
+
263
+ .connectivity-error {
264
+ padding: 12px 16px;
265
+ background-color: #E50045;
266
+ border-radius: 8px;
267
+ width: 500px;
268
+ color: #FFFFFF;
269
+ border: 1px solid #A80033;
270
+ text-align: center;
271
+ margin: 0;
272
+ }
273
+
274
+ .connectivity-error a {
275
+ color: #FFFFFF;
276
+ text-decoration: underline;
277
+ }
278
+ \`;
279
+ `;
280
+ }
281
+ exports.getSentryExamplePageContents = getSentryExamplePageContents;
282
+ function getSentryExampleApiContents(options) {
283
+ return `import * as Sentry from '@sentry/react-router';
284
+
285
+ class SentryExampleBackendError extends Error {
286
+ constructor(message${options.isTS ? ': string | undefined' : ''}) {
287
+ super(message);
288
+ this.name = "SentryExampleBackendError";
289
+ }
290
+ }
291
+
292
+ export async function loader() {
293
+ await Sentry.startSpan({
294
+ name: 'Example Backend Span',
295
+ op: 'test'
296
+ }, async () => {
297
+ // Simulate some backend work
298
+ await new Promise(resolve => setTimeout(resolve, 100));
299
+ });
300
+
301
+ throw new SentryExampleBackendError("This error is raised on the backend API route.");
302
+ }
303
+ `;
304
+ }
305
+ exports.getSentryExampleApiContents = getSentryExampleApiContents;
306
+ //# sourceMappingURL=sdk-example.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-example.js","sourceRoot":"","sources":["../../../src/react-router/sdk-example.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\n// @ts-expect-error - clack is ESM and TS complains about that. It works though\nimport clack from '@clack/prompts';\nimport { addRoutesToConfig } from './codemods/routes-config';\nimport { getRouteFilePath } from './sdk-setup';\n\nexport async function createExamplePage(options: {\n selfHosted: boolean;\n orgSlug: string;\n projectId: string;\n url: string;\n isTS: boolean;\n projectDir: string;\n}) {\n const routesPath = path.join(options.projectDir, 'app', 'routes');\n\n if (!fs.existsSync(routesPath)) {\n fs.mkdirSync(routesPath, { recursive: true });\n }\n\n const exampleRoutePath = getRouteFilePath(\n 'sentry-example-page',\n options.isTS,\n );\n\n if (fs.existsSync(exampleRoutePath)) {\n clack.log.warn(\n `It seems like a sentry example page already exists (${path.basename(\n exampleRoutePath,\n )}). Skipping creation of example route.`,\n );\n return;\n }\n\n await fs.promises.writeFile(\n exampleRoutePath,\n getSentryExamplePageContents(options),\n );\n\n // Create the API route for backend error testing\n const apiRoutePath = path.join(\n routesPath,\n `api.sentry-example-api.${options.isTS ? 'ts' : 'js'}`,\n );\n\n if (!fs.existsSync(apiRoutePath)) {\n await fs.promises.writeFile(\n apiRoutePath,\n getSentryExampleApiContents(options),\n );\n clack.log.info(`Created sentry example API route at ${apiRoutePath}.`);\n }\n\n // Check if there's a routes.ts configuration file and add the route using codemod\n const routesConfigPath = path.join(options.projectDir, 'app', 'routes.ts');\n if (fs.existsSync(routesConfigPath)) {\n try {\n await addRoutesToConfig(routesConfigPath, options.isTS);\n } catch (e) {\n clack.log.warn(\n `Could not update routes.ts configuration: ${\n e instanceof Error ? e.message : String(e)\n }`,\n );\n clack.log.info(\n 'Please manually add these routes to your routes.ts file: route(\"/sentry-example-page\", \"routes/sentry-example-page.tsx\") and route(\"/api/sentry-example-api\", \"routes/api.sentry-example-api.ts\")',\n );\n }\n }\n\n clack.log.info(`Created sentry example page at ${exampleRoutePath}.`);\n}\n\nexport function getSentryExamplePageContents(options: {\n selfHosted: boolean;\n orgSlug: string;\n projectId: string;\n url: string;\n isTS?: boolean;\n}) {\n const issuesPageLink = options.selfHosted\n ? `${options.url}organizations/${options.orgSlug}/issues/?project=${options.projectId}`\n : `https://${options.orgSlug}.sentry.io/issues/?project=${options.projectId}`;\n\n return `import * as Sentry from '@sentry/react-router';\nimport { useState, useEffect } from 'react';\n\nclass SentryExampleFrontendError extends Error {\n constructor(message${options.isTS ? ': string | undefined' : ''}) {\n super(message);\n this.name = \"SentryExampleFrontendError\";\n }\n}\n\nexport const meta = () => {\n return [\n { title: \"sentry-example-page\" },\n ];\n}\n\nexport default function SentryExamplePage() {\n const [hasSentError, setHasSentError] = useState(false);\n const [isConnected, setIsConnected] = useState(true);\n\n useEffect(() => {\n async function checkConnectivity() {\n const result = await Sentry.diagnoseSdkConnectivity();\n setIsConnected(result !== 'sentry-unreachable');\n }\n checkConnectivity();\n }, [setIsConnected]);\n\n return (\n <div>\n <main>\n <div className=\"flex-spacer\" />\n <svg height=\"40\" width=\"40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M21.85 2.995a3.698 3.698 0 0 1 1.353 1.354l16.303 28.278a3.703 3.703 0 0 1-1.354 5.053 3.694 3.694 0 0 1-1.848.496h-3.828a31.149 31.149 0 0 0 0-3.09h3.815a.61.61 0 0 0 .537-.917L20.523 5.893a.61.61 0 0 0-1.057 0l-3.739 6.494a28.948 28.948 0 0 1 9.63 10.453 28.988 28.988 0 0 1 3.499 13.78v1.542h-9.852v-1.544a19.106 19.106 0 0 0-2.182-8.85 19.08 19.08 0 0 0-6.032-6.829l-1.85 3.208a15.377 15.377 0 0 1 6.382 12.484v1.542H3.696A3.694 3.694 0 0 1 0 34.473c0-.648.17-1.286.494-1.849l2.33-4.074a8.562 8.562 0 0 1 2.689 1.536L3.158 34.17a.611.611 0 0 0 .538.917h8.448a12.481 12.481 0 0 0-6.037-9.09l-1.344-.772 4.908-8.545 1.344.77a22.16 22.16 0 0 1 7.705 7.444 22.193 22.193 0 0 1 3.316 10.193h3.699a25.892 25.892 0 0 0-3.811-12.033 25.856 25.856 0 0 0-9.046-8.796l-1.344-.772 5.269-9.136a3.698 3.698 0 0 1 3.2-1.849c.648 0 1.285.17 1.847.495Z\" fill=\"currentcolor\"/>\n </svg>\n <h1>\n sentry-example-page\n </h1>\n\n <p className=\"description\">\n Click the button below, and view the sample error on the Sentry <a target=\"_blank\" rel=\"noreferrer\" href=\"${issuesPageLink}\">Issues Page</a>.\n For more details about setting up Sentry, <a target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.sentry.io/platforms/javascript/guides/react-router/\">read our docs</a>.\n </p>\n\n <button\n type=\"button\"\n onClick={async () => {\n await Sentry.startSpan({\n name: 'Example Frontend Span',\n op: 'test'\n }, async () => {\n const res = await fetch(\"/api/sentry-example-api\");\n if (!res.ok) {\n setHasSentError(true);\n }\n });\n throw new SentryExampleFrontendError(\"This error is raised on the frontend of the example page.\");\n }}\n disabled={!isConnected}\n >\n <span>\n Throw Sample Error\n </span>\n </button>\n\n {hasSentError ? (\n <p className=\"success\">\n Sample error was sent to Sentry.\n </p>\n ) : !isConnected ? (\n <div className=\"connectivity-error\">\n <p>It looks like network requests to Sentry are being blocked, which will prevent errors from being captured. Try disabling your ad-blocker to complete the test.</p>\n </div>\n ) : (\n <div className=\"success_placeholder\" />\n )}\n\n <div className=\"flex-spacer\" />\n </main>\n\n {/* Not for production use! We're just saving you from having to delete an extra CSS file ;) */}\n <style dangerouslySetInnerHTML={{ __html: styles }}></style>\n </div>\n );\n}\n\nconst styles = \\`\n main {\n display: flex;\n min-height: 100vh;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n gap: 16px;\n padding: 16px;\n font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif;\n }\n\n h1 {\n padding: 0px 4px;\n border-radius: 4px;\n background-color: rgba(24, 20, 35, 0.03);\n font-family: monospace;\n font-size: 20px;\n line-height: 1.2;\n }\n\n p {\n margin: 0;\n font-size: 20px;\n }\n\n a {\n color: #6341F0;\n text-decoration: underline;\n cursor: pointer;\n\n @media (prefers-color-scheme: dark) {\n color: #B3A1FF;\n }\n }\n\n button {\n border-radius: 8px;\n color: white;\n cursor: pointer;\n background-color: #553DB8;\n border: none;\n padding: 0;\n margin-top: 4px;\n\n & > span {\n display: inline-block;\n padding: 12px 16px;\n border-radius: inherit;\n font-size: 20px;\n font-weight: bold;\n line-height: 1;\n background-color: #7553FF;\n border: 1px solid #553DB8;\n transform: translateY(-4px);\n }\n\n &:hover > span {\n transform: translateY(-8px);\n }\n\n &:active > span {\n transform: translateY(0);\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n\n & > span {\n transform: translateY(0);\n border: none;\n }\n }\n }\n\n .description {\n text-align: center;\n color: #6E6C75;\n max-width: 500px;\n line-height: 1.5;\n font-size: 20px;\n\n @media (prefers-color-scheme: dark) {\n color: #A49FB5;\n }\n }\n\n .flex-spacer {\n flex: 1;\n }\n\n .success {\n padding: 12px 16px;\n border-radius: 8px;\n font-size: 20px;\n line-height: 1;\n background-color: #00F261;\n border: 1px solid #00BF4D;\n color: #181423;\n }\n\n .success_placeholder {\n height: 46px;\n }\n\n .connectivity-error {\n padding: 12px 16px;\n background-color: #E50045;\n border-radius: 8px;\n width: 500px;\n color: #FFFFFF;\n border: 1px solid #A80033;\n text-align: center;\n margin: 0;\n }\n\n .connectivity-error a {\n color: #FFFFFF;\n text-decoration: underline;\n }\n\\`;\n`;\n}\n\nexport function getSentryExampleApiContents(options: { isTS?: boolean }) {\n return `import * as Sentry from '@sentry/react-router';\n\nclass SentryExampleBackendError extends Error {\n constructor(message${options.isTS ? ': string | undefined' : ''}) {\n super(message);\n this.name = \"SentryExampleBackendError\";\n }\n}\n\nexport async function loader() {\n await Sentry.startSpan({\n name: 'Example Backend Span',\n op: 'test'\n }, async () => {\n // Simulate some backend work\n await new Promise(resolve => setTimeout(resolve, 100));\n });\n\n throw new SentryExampleBackendError(\"This error is raised on the backend API route.\");\n}\n`;\n}\n"]}