@sentry/wizard 3.0.0 → 3.1.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 (52) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/bin.ts +9 -2
  3. package/dist/bin.js +10 -2
  4. package/dist/bin.js.map +1 -1
  5. package/dist/lib/Helper/MergeConfig.js +1 -0
  6. package/dist/lib/Helper/MergeConfig.js.map +1 -1
  7. package/dist/lib/Steps/BaseStep.js.map +1 -1
  8. package/dist/lib/Steps/Initial.js +1 -0
  9. package/dist/lib/Steps/Initial.js.map +1 -1
  10. package/dist/lib/Steps/Integrations/BaseIntegration.js +2 -0
  11. package/dist/lib/Steps/Integrations/BaseIntegration.js.map +1 -1
  12. package/dist/lib/Steps/Integrations/Cordova.js +1 -0
  13. package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
  14. package/dist/lib/Steps/Integrations/Electron.js +2 -0
  15. package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
  16. package/dist/lib/Steps/Integrations/MobileProject.js +1 -0
  17. package/dist/lib/Steps/Integrations/MobileProject.js.map +1 -1
  18. package/dist/lib/Steps/Integrations/NextJs.js +1 -0
  19. package/dist/lib/Steps/Integrations/NextJs.js.map +1 -1
  20. package/dist/lib/Steps/Integrations/ReactNative.js +2 -1
  21. package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
  22. package/dist/lib/Steps/Result.js +1 -0
  23. package/dist/lib/Steps/Result.js.map +1 -1
  24. package/dist/lib/Steps/WaitForSentry.js +42 -47
  25. package/dist/lib/Steps/WaitForSentry.js.map +1 -1
  26. package/dist/lib/Steps/Welcome.js +1 -0
  27. package/dist/lib/Steps/Welcome.js.map +1 -1
  28. package/dist/src/clack-utils.d.ts +40 -0
  29. package/dist/src/clack-utils.js +329 -0
  30. package/dist/src/clack-utils.js.map +1 -0
  31. package/dist/src/nextjs-wizard.d.ts +5 -0
  32. package/dist/src/nextjs-wizard.js +321 -0
  33. package/dist/src/nextjs-wizard.js.map +1 -0
  34. package/dist/src/templates/nextjs-templates.d.ts +13 -0
  35. package/dist/src/templates/nextjs-templates.js +54 -0
  36. package/dist/src/templates/nextjs-templates.js.map +1 -0
  37. package/lib/Helper/MergeConfig.ts +1 -0
  38. package/lib/Steps/BaseStep.ts +1 -1
  39. package/lib/Steps/Initial.ts +1 -0
  40. package/lib/Steps/Integrations/BaseIntegration.ts +2 -0
  41. package/lib/Steps/Integrations/Cordova.ts +2 -1
  42. package/lib/Steps/Integrations/Electron.ts +3 -1
  43. package/lib/Steps/Integrations/MobileProject.ts +1 -0
  44. package/lib/Steps/Integrations/NextJs.ts +1 -0
  45. package/lib/Steps/Integrations/ReactNative.ts +3 -2
  46. package/lib/Steps/Result.ts +1 -0
  47. package/lib/Steps/WaitForSentry.ts +1 -1
  48. package/lib/Steps/Welcome.ts +2 -1
  49. package/package.json +10 -3
  50. package/src/clack-utils.ts +346 -0
  51. package/src/nextjs-wizard.ts +422 -0
  52. package/src/templates/nextjs-templates.ts +252 -0
@@ -0,0 +1,422 @@
1
+ /* eslint-disable max-lines */
2
+ import * as clack from '@clack/prompts';
3
+ import chalk from 'chalk';
4
+ import * as fs from 'fs';
5
+ import { builders, generateCode, parseModule } from 'magicast';
6
+ import * as path from 'path';
7
+
8
+ import {
9
+ abort,
10
+ abortIfCancelled,
11
+ addSentryCliRc,
12
+ askForSelfHosted,
13
+ askForWizardLogin,
14
+ confirmContinueEvenThoughNoGitRepo,
15
+ installPackage,
16
+ printWelcome,
17
+ SentryProjectData,
18
+ } from './clack-utils';
19
+ import {
20
+ getNextjsConfigCjsAppendix,
21
+ getNextjsConfigCjsTemplate,
22
+ getNextjsConfigEsmCopyPasteSnippet,
23
+ getNextjsSentryBuildOptionsTemplate,
24
+ getNextjsWebpackPluginOptionsTemplate,
25
+ getSentryConfigContents,
26
+ getSentryExampleApiRoute,
27
+ getSentryExamplePageContents,
28
+ } from './templates/nextjs-templates';
29
+
30
+ interface NextjsWizardOptions {
31
+ promoCode?: string;
32
+ }
33
+
34
+ // eslint-disable-next-line complexity
35
+ export async function runNextjsWizard(
36
+ options: NextjsWizardOptions,
37
+ ): Promise<void> {
38
+ printWelcome({
39
+ wizardName: 'Sentry Next.js Wizard',
40
+ promoCode: options.promoCode,
41
+ });
42
+
43
+ await confirmContinueEvenThoughNoGitRepo();
44
+
45
+ const packageJsonFileContents = await fs.promises
46
+ .readFile(path.join(process.cwd(), 'package.json'), 'utf8')
47
+ .catch(() => {
48
+ clack.log.error(
49
+ 'Could not find package.json. Make sure to run the wizard in the root of your Next.js app!',
50
+ );
51
+ abort();
52
+ });
53
+
54
+ let packageJson:
55
+ | { dependencies?: { ['@sentry/nextjs']: string; ['next']: string } }
56
+ | undefined = undefined;
57
+
58
+ try {
59
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
60
+ packageJson = JSON.parse(packageJsonFileContents);
61
+ } catch (e) {
62
+ clack.log.error(
63
+ 'Unable to parse your package.json. Make sure it has a valid format!',
64
+ );
65
+
66
+ abort();
67
+ }
68
+
69
+ if (!packageJson?.dependencies?.['next']) {
70
+ const continueWithoutNext = await clack.confirm({
71
+ message:
72
+ 'Next.js does not seem to be installed. Do you still want to continue?',
73
+ initialValue: false,
74
+ });
75
+
76
+ abortIfCancelled(continueWithoutNext);
77
+
78
+ if (!continueWithoutNext) {
79
+ abort();
80
+ }
81
+ }
82
+
83
+ const { url: sentryUrl, selfHosted } = await askForSelfHosted();
84
+
85
+ const { projects, apiKeys } = await askForWizardLogin({
86
+ promoCode: options.promoCode,
87
+ url: sentryUrl,
88
+ });
89
+
90
+ const selectedProject: SentryProjectData | symbol = await clack.select({
91
+ message: 'Select your Sentry project.',
92
+ options: projects.map(project => {
93
+ return {
94
+ value: project,
95
+ label: `${project.organization.slug}/${project.slug}`,
96
+ };
97
+ }),
98
+ });
99
+
100
+ abortIfCancelled(selectedProject);
101
+
102
+ await installPackage({
103
+ packageName: '@sentry/nextjs',
104
+ alreadyInstalled: !!packageJson?.dependencies?.['@sentry/nextjs'],
105
+ });
106
+
107
+ let isUsingTypescript = false;
108
+ try {
109
+ isUsingTypescript = fs.existsSync(
110
+ path.join(process.cwd(), 'tsconfig.json'),
111
+ );
112
+ } catch (e) {
113
+ // noop - Default to assuming user is not using typescript
114
+ }
115
+
116
+ const configVariants = ['server', 'client', 'edge'] as const;
117
+
118
+ for (const configVariant of configVariants) {
119
+ const jsConfig = `sentry.${configVariant}.config.js`;
120
+ const tsConfig = `sentry.${configVariant}.config.ts`;
121
+
122
+ const jsConfigExists = fs.existsSync(path.join(process.cwd(), jsConfig));
123
+ const tsConfigExists = fs.existsSync(path.join(process.cwd(), tsConfig));
124
+
125
+ let shouldWriteFile = true;
126
+
127
+ if (jsConfigExists || tsConfigExists) {
128
+ const existingConfigs = [];
129
+
130
+ if (jsConfigExists) {
131
+ existingConfigs.push(jsConfig);
132
+ }
133
+
134
+ if (tsConfigExists) {
135
+ existingConfigs.push(tsConfig);
136
+ }
137
+
138
+ const overwriteExistingConfigs = await clack.confirm({
139
+ message: `Found existing Sentry ${configVariant} config (${existingConfigs.join(
140
+ ', ',
141
+ )}). Overwrite ${existingConfigs.length > 1 ? 'them' : 'it'}?`,
142
+ });
143
+
144
+ abortIfCancelled(overwriteExistingConfigs);
145
+
146
+ shouldWriteFile = overwriteExistingConfigs;
147
+
148
+ if (overwriteExistingConfigs) {
149
+ if (jsConfigExists) {
150
+ fs.unlinkSync(path.join(process.cwd(), jsConfig));
151
+ clack.log.warn(`Removed existing ${chalk.bold(jsConfig)}.`);
152
+ }
153
+ if (tsConfigExists) {
154
+ fs.unlinkSync(path.join(process.cwd(), tsConfig));
155
+ clack.log.warn(`Removed existing ${chalk.bold(tsConfig)}.`);
156
+ }
157
+ }
158
+ }
159
+
160
+ if (shouldWriteFile) {
161
+ await fs.promises.writeFile(
162
+ path.join(process.cwd(), isUsingTypescript ? tsConfig : jsConfig),
163
+ getSentryConfigContents(
164
+ selectedProject.keys[0].dsn.public,
165
+ configVariant,
166
+ ),
167
+ { encoding: 'utf8', flag: 'w' },
168
+ );
169
+ clack.log.success(
170
+ `Created fresh ${chalk.bold(isUsingTypescript ? tsConfig : jsConfig)}.`,
171
+ );
172
+ }
173
+ }
174
+
175
+ const sentryWebpackOptionsTemplate = getNextjsWebpackPluginOptionsTemplate(
176
+ selectedProject.organization.slug,
177
+ selectedProject.slug,
178
+ );
179
+ const sentryBuildOptionsTemplate = getNextjsSentryBuildOptionsTemplate();
180
+
181
+ const nextConfigJs = 'next.config.js';
182
+ const nextConfigMjs = 'next.config.mjs';
183
+
184
+ const nextConfigJsExists = fs.existsSync(
185
+ path.join(process.cwd(), nextConfigJs),
186
+ );
187
+ const nextConfigMjsExists = fs.existsSync(
188
+ path.join(process.cwd(), nextConfigMjs),
189
+ );
190
+
191
+ if (!nextConfigJsExists && !nextConfigMjsExists) {
192
+ await fs.promises.writeFile(
193
+ path.join(process.cwd(), nextConfigJs),
194
+ getNextjsConfigCjsTemplate(
195
+ sentryWebpackOptionsTemplate,
196
+ sentryBuildOptionsTemplate,
197
+ ),
198
+ { encoding: 'utf8', flag: 'w' },
199
+ );
200
+
201
+ clack.log.success(
202
+ `Created ${chalk.bold('next.config.js')} with Sentry configuration.`,
203
+ );
204
+ }
205
+
206
+ if (nextConfigJsExists) {
207
+ const nextConfgiJsContent = fs.readFileSync(
208
+ path.join(process.cwd(), nextConfigJs),
209
+ 'utf8',
210
+ );
211
+
212
+ const probablyIncludesSdk =
213
+ nextConfgiJsContent.includes('@sentry/nextjs') &&
214
+ nextConfgiJsContent.includes('withSentryConfig');
215
+
216
+ let shouldInject = true;
217
+
218
+ if (probablyIncludesSdk) {
219
+ const injectAnyhow = await clack.confirm({
220
+ message: `${chalk.bold(
221
+ nextConfigMjs,
222
+ )} already contains Sentry SDK configuration. Should the wizard modify it anyways?`,
223
+ });
224
+
225
+ abortIfCancelled(injectAnyhow);
226
+
227
+ shouldInject = injectAnyhow;
228
+ }
229
+
230
+ if (shouldInject) {
231
+ await fs.promises.appendFile(
232
+ path.join(process.cwd(), nextConfigJs),
233
+ getNextjsConfigCjsAppendix(
234
+ sentryWebpackOptionsTemplate,
235
+ sentryBuildOptionsTemplate,
236
+ ),
237
+ 'utf8',
238
+ );
239
+
240
+ clack.log.success(
241
+ `Added Sentry configuration to ${chalk.bold(nextConfigJs)}. ${chalk.dim(
242
+ '(you probably want to clean this up a bit!)',
243
+ )}`,
244
+ );
245
+ }
246
+ }
247
+
248
+ if (nextConfigMjsExists) {
249
+ const nextConfgiMjsContent = fs.readFileSync(
250
+ path.join(process.cwd(), nextConfigMjs),
251
+ 'utf8',
252
+ );
253
+
254
+ const probablyIncludesSdk =
255
+ nextConfgiMjsContent.includes('@sentry/nextjs') &&
256
+ nextConfgiMjsContent.includes('withSentryConfig');
257
+
258
+ let shouldInject = true;
259
+
260
+ if (probablyIncludesSdk) {
261
+ const injectAnyhow = await clack.confirm({
262
+ message: `${chalk.bold(
263
+ nextConfigMjs,
264
+ )} already contains Sentry SDK configuration. Should the wizard modify it anyways?`,
265
+ });
266
+
267
+ abortIfCancelled(injectAnyhow);
268
+ shouldInject = injectAnyhow;
269
+ }
270
+
271
+ try {
272
+ if (shouldInject) {
273
+ const mod = parseModule(nextConfgiMjsContent);
274
+ mod.imports.$add({
275
+ from: '@sentry/nextjs',
276
+ imported: 'withSentryConfig',
277
+ local: 'withSentryConfig',
278
+ });
279
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
280
+ const expressionToWrap = generateCode(mod.exports.default.$ast).code;
281
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
282
+ mod.exports.default = builders.raw(`withSentryConfig(
283
+ ${expressionToWrap},
284
+ ${sentryWebpackOptionsTemplate},
285
+ ${sentryBuildOptionsTemplate}
286
+ )`);
287
+ const newCode = mod.generate().code;
288
+
289
+ await fs.promises.writeFile(
290
+ path.join(process.cwd(), nextConfigMjs),
291
+ newCode,
292
+ {
293
+ encoding: 'utf8',
294
+ flag: 'w',
295
+ },
296
+ );
297
+ clack.log.success(
298
+ `Added Sentry configuration to ${chalk.bold(
299
+ nextConfigMjs,
300
+ )}. ${chalk.dim('(you probably want to clean this up a bit!)')}`,
301
+ );
302
+ }
303
+ } catch (e) {
304
+ clack.log.warn(
305
+ chalk.yellow(
306
+ `Something went wrong writing to ${chalk.bold(nextConfigMjs)}`,
307
+ ),
308
+ );
309
+ clack.log.info(
310
+ `Please put the following code snippet into ${chalk.bold(
311
+ nextConfigMjs,
312
+ )}: ${chalk.dim('You probably have to clean it up a bit.')}\n`,
313
+ );
314
+
315
+ // eslint-disable-next-line no-console
316
+ console.log(
317
+ getNextjsConfigEsmCopyPasteSnippet(
318
+ sentryWebpackOptionsTemplate,
319
+ sentryBuildOptionsTemplate,
320
+ ),
321
+ );
322
+
323
+ const shouldContinue = await clack.confirm({
324
+ message: `Are you done putting the snippet above into ${chalk.bold(
325
+ nextConfigMjs,
326
+ )}?`,
327
+ active: 'Yes',
328
+ inactive: 'No, get me out of here',
329
+ });
330
+
331
+ abortIfCancelled(shouldContinue);
332
+ if (!shouldContinue) {
333
+ abort();
334
+ }
335
+ }
336
+ }
337
+
338
+ const maybePagesDirPath = path.join(process.cwd(), 'pages');
339
+ const maybeSrcPagesDirPath = path.join(process.cwd(), 'src', 'pages');
340
+
341
+ let pagesLocation =
342
+ fs.existsSync(maybePagesDirPath) &&
343
+ fs.lstatSync(maybePagesDirPath).isDirectory()
344
+ ? ['pages']
345
+ : fs.existsSync(maybeSrcPagesDirPath) &&
346
+ fs.lstatSync(maybeSrcPagesDirPath).isDirectory()
347
+ ? ['src', 'pages']
348
+ : undefined;
349
+
350
+ if (!pagesLocation) {
351
+ pagesLocation = ['pages'];
352
+ fs.mkdirSync(path.join(process.cwd(), ...pagesLocation), {
353
+ recursive: true,
354
+ });
355
+ }
356
+
357
+ if (pagesLocation) {
358
+ const examplePageContents = getSentryExamplePageContents({
359
+ selfHosted,
360
+ orgSlug: selectedProject.organization.slug,
361
+ projectId: selectedProject.id,
362
+ url: sentryUrl,
363
+ });
364
+
365
+ await fs.promises.writeFile(
366
+ path.join(process.cwd(), ...pagesLocation, 'sentry-example-page.js'),
367
+ examplePageContents,
368
+ { encoding: 'utf8', flag: 'w' },
369
+ );
370
+
371
+ clack.log.success(
372
+ `Created ${chalk.bold(
373
+ path.join(...pagesLocation, 'sentry-example-page.js'),
374
+ )}.`,
375
+ );
376
+
377
+ fs.mkdirSync(path.join(process.cwd(), ...pagesLocation, 'api'), {
378
+ recursive: true,
379
+ });
380
+
381
+ await fs.promises.writeFile(
382
+ path.join(
383
+ process.cwd(),
384
+ ...pagesLocation,
385
+ 'api',
386
+ 'sentry-example-api.js',
387
+ ),
388
+ getSentryExampleApiRoute(),
389
+ { encoding: 'utf8', flag: 'w' },
390
+ );
391
+
392
+ clack.log.success(
393
+ `Created ${chalk.bold(
394
+ path.join(...pagesLocation, 'api', 'sentry-example-api.js'),
395
+ )}.`,
396
+ );
397
+ }
398
+
399
+ await addSentryCliRc(apiKeys.token);
400
+
401
+ const mightBeUsingVercel = fs.existsSync(
402
+ path.join(process.cwd(), 'vercel.json'),
403
+ );
404
+
405
+ clack.outro(
406
+ `${chalk.green('Everything is set up!')}
407
+
408
+ ${chalk.cyan(
409
+ 'You can validate your setup by starting your dev environment (`next dev`) and visiting "/sentry-example-page".',
410
+ )}
411
+ ${
412
+ mightBeUsingVercel
413
+ ? `
414
+ ▲ It seems like you're using Vercel. We recommend using the Sentry Vercel integration: https://vercel.com/integrations/sentry
415
+ `
416
+ : ''
417
+ }
418
+ ${chalk.dim(
419
+ 'If you encounter any issues, let us know here: https://github.com/getsentry/sentry-javascript/issues',
420
+ )}`,
421
+ );
422
+ }
@@ -0,0 +1,252 @@
1
+ export function getNextjsWebpackPluginOptionsTemplate(
2
+ orgSlug: string,
3
+ projectSlug: string,
4
+ ): string {
5
+ return `{
6
+ // For all available options, see:
7
+ // https://github.com/getsentry/sentry-webpack-plugin#options
8
+
9
+ // Suppresses source map uploading logs during build
10
+ silent: true,
11
+
12
+ org: "${orgSlug}",
13
+ project: "${projectSlug}",
14
+ }`;
15
+ }
16
+
17
+ export function getNextjsSentryBuildOptionsTemplate(): string {
18
+ return `{
19
+ // For all available options, see:
20
+ // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
21
+
22
+ // Upload a larger set of source maps for prettier stack traces (increases build time)
23
+ widenClientFileUpload: true,
24
+
25
+ // Transpiles SDK to be compatible with IE11 (increases bundle size)
26
+ transpileClientSDK: true,
27
+
28
+ // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
29
+ tunnelRoute: "/monitoring",
30
+
31
+ // Hides source maps from generated client bundles
32
+ hideSourceMaps: true,
33
+
34
+ // Automatically tree-shake Sentry logger statements to reduce bundle size
35
+ disableLogger: true,
36
+ }`;
37
+ }
38
+
39
+ export function getNextjsConfigCjsTemplate(
40
+ sentryWebpackPluginOptionsTemplate: string,
41
+ sentryBuildOptionsTemplate: string,
42
+ ): string {
43
+ return `const { withSentryConfig } = require("@sentry/nextjs");
44
+
45
+ /** @type {import('next').NextConfig} */
46
+ const nextConfig = {};
47
+
48
+ module.exports = withSentryConfig(
49
+ nextConfig,
50
+ ${sentryWebpackPluginOptionsTemplate},
51
+ ${sentryBuildOptionsTemplate}
52
+ );
53
+ `;
54
+ }
55
+
56
+ export function getNextjsConfigCjsAppendix(
57
+ sentryWebpackPluginOptionsTemplate: string,
58
+ sentryBuildOptionsTemplate: string,
59
+ ): string {
60
+ return `
61
+
62
+ // Inected Content via Sentry Wizard Below
63
+
64
+ const { withSentryConfig } = require("@sentry/nextjs");
65
+
66
+ module.exports = withSentryConfig(
67
+ module.exports,
68
+ ${sentryWebpackPluginOptionsTemplate},
69
+ ${sentryBuildOptionsTemplate}
70
+ );
71
+ `;
72
+ }
73
+
74
+ export function getNextjsConfigEsmCopyPasteSnippet(
75
+ sentryWebpackPluginOptionsTemplate: string,
76
+ sentryBuildOptionsTemplate: string,
77
+ ): string {
78
+ return `
79
+
80
+ // next.config.mjs
81
+ import { withSentryConfig } from "@sentry/nextjs";
82
+
83
+ export default withSentryConfig(
84
+ yourNextConfig,
85
+ ${sentryWebpackPluginOptionsTemplate},
86
+ ${sentryBuildOptionsTemplate}
87
+ );
88
+ `;
89
+ }
90
+
91
+ export function getSentryConfigContents(
92
+ dsn: string,
93
+ config: 'server' | 'client' | 'edge',
94
+ ): string {
95
+ let primer;
96
+ if (config === 'server') {
97
+ primer = `// This file configures the initialization of Sentry on the server.
98
+ // The config you add here will be used whenever the server handles a request.
99
+ // https://docs.sentry.io/platforms/javascript/guides/nextjs/`;
100
+ } else if (config === 'client') {
101
+ primer = `// This file configures the initialization of Sentry on the client.
102
+ // The config you add here will be used whenever a users loads a page in their browser.
103
+ // https://docs.sentry.io/platforms/javascript/guides/nextjs/`;
104
+ } else if (config === 'edge') {
105
+ primer = `// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
106
+ // The config you add here will be used whenever one of the edge features is loaded.
107
+ // Note that this config is unrelated to the Verel Edge Runtime and is also required when running locally.
108
+ // https://docs.sentry.io/platforms/javascript/guides/nextjs/`;
109
+ }
110
+
111
+ let additionalOptions = '';
112
+ if (config === 'client') {
113
+ additionalOptions = `
114
+
115
+ replaysOnErrorSampleRate: 1.0,
116
+
117
+ // This sets the sample rate to be 10%. You may want this to be 100% while
118
+ // in development and sample at a lower rate in production
119
+ replaysSessionSampleRate: 0.1,
120
+
121
+ // You can remove this option if you're not planning to use the Sentry Session Replay feature:
122
+ integrations: [
123
+ new Sentry.Replay({
124
+ // Additional Replay configuration goes in here, for example:
125
+ maskAllText: true,
126
+ blockAllMedia: true,
127
+ }),
128
+ ],`;
129
+ }
130
+
131
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
132
+ return `${primer}
133
+
134
+ import * as Sentry from "@sentry/nextjs";
135
+
136
+ Sentry.init({
137
+ dsn: "${dsn}",
138
+
139
+ // Adjust this value in production, or use tracesSampler for greater control
140
+ tracesSampleRate: 1,
141
+
142
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
143
+ debug: false,${additionalOptions}
144
+ });
145
+ `;
146
+ }
147
+
148
+ export function getSentryExamplePageContents(options: {
149
+ selfHosted: boolean;
150
+ url: string;
151
+ orgSlug: string;
152
+ projectId: string;
153
+ }): string {
154
+ const issuesPageLink = options.selfHosted
155
+ ? `${options.url}organizations/${options.orgSlug}/issues/?project=${options.projectId}`
156
+ : `https://${options.orgSlug}.sentry.io/issues/?project=${options.projectId}`;
157
+
158
+ return `import Head from "next/head";
159
+ import * as Sentry from "@sentry/nextjs";
160
+
161
+ export default function Home() {
162
+ return (
163
+ <div>
164
+ <Head>
165
+ <title>Sentry Onboarding</title>
166
+ <meta name="description" content="Test Sentry for your Next.js app!" />
167
+ </Head>
168
+
169
+ <main
170
+ style={{
171
+ minHeight: "100vh",
172
+ display: "flex",
173
+ flexDirection: "column",
174
+ justifyContent: "center",
175
+ alignItems: "center",
176
+ }}
177
+ >
178
+ <h1 style={{ fontSize: "4rem", margin: "14px 0" }}>
179
+ <svg
180
+ style={{
181
+ height: "1em",
182
+ }}
183
+ xmlns="http://www.w3.org/2000/svg"
184
+ viewBox="0 0 200 44"
185
+ >
186
+ <path
187
+ fill="currentColor"
188
+ d="M124.32,28.28,109.56,9.22h-3.68V34.77h3.73V15.19l15.18,19.58h3.26V9.22h-3.73ZM87.15,23.54h13.23V20.22H87.14V12.53h14.93V9.21H83.34V34.77h18.92V31.45H87.14ZM71.59,20.3h0C66.44,19.06,65,18.08,65,15.7c0-2.14,1.89-3.59,4.71-3.59a12.06,12.06,0,0,1,7.07,2.55l2-2.83a14.1,14.1,0,0,0-9-3c-5.06,0-8.59,3-8.59,7.27,0,4.6,3,6.19,8.46,7.52C74.51,24.74,76,25.78,76,28.11s-2,3.77-5.09,3.77a12.34,12.34,0,0,1-8.3-3.26l-2.25,2.69a15.94,15.94,0,0,0,10.42,3.85c5.48,0,9-2.95,9-7.51C79.75,23.79,77.47,21.72,71.59,20.3ZM195.7,9.22l-7.69,12-7.64-12h-4.46L186,24.67V34.78h3.84V24.55L200,9.22Zm-64.63,3.46h8.37v22.1h3.84V12.68h8.37V9.22H131.08ZM169.41,24.8c3.86-1.07,6-3.77,6-7.63,0-4.91-3.59-8-9.38-8H154.67V34.76h3.8V25.58h6.45l6.48,9.2h4.44l-7-9.82Zm-10.95-2.5V12.6h7.17c3.74,0,5.88,1.77,5.88,4.84s-2.29,4.86-5.84,4.86Z M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z"
189
+ ></path>
190
+ </svg>
191
+ </h1>
192
+
193
+ <p>Get started by sending us a sample error:</p>
194
+ <button
195
+ type="button"
196
+ style={{
197
+ padding: "12px",
198
+ cursor: "pointer",
199
+ backgroundColor: "#AD6CAA",
200
+ borderRadius: "4px",
201
+ border: "none",
202
+ color: "white",
203
+ fontSize: "14px",
204
+ margin: "18px",
205
+ }}
206
+ onClick={async () => {
207
+ const transaction = Sentry.startTransaction({
208
+ name: "Example Frontend Transaction",
209
+ });
210
+
211
+ Sentry.configureScope((scope) => {
212
+ scope.setSpan(transaction);
213
+ });
214
+
215
+ try {
216
+ const res = await fetch("/api/sentry-example-api");
217
+ if (!res.ok) {
218
+ throw new Error("Sentry Example Frontend Error");
219
+ }
220
+ } finally {
221
+ transaction.finish();
222
+ }
223
+ }}
224
+ >
225
+ Throw error!
226
+ </button>
227
+
228
+ <p>
229
+ Next, look for the error on the{" "}
230
+ <a href="${issuesPageLink}">Issues Page</a>.
231
+ </p>
232
+ <p style={{ marginTop: "24px" }}>
233
+ For more information, see{" "}
234
+ <a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/">
235
+ https://docs.sentry.io/platforms/javascript/guides/nextjs/
236
+ </a>
237
+ </p>
238
+ </main>
239
+ </div>
240
+ );
241
+ }
242
+ `;
243
+ }
244
+
245
+ export function getSentryExampleApiRoute() {
246
+ return `// A faulty API route to test Sentry's error monitoring
247
+ export default function handler(_req, res) {
248
+ throw new Error("Sentry Example API Route Error");
249
+ res.status(200).json({ name: "John Doe" });
250
+ }
251
+ `;
252
+ }