@sentry/wizard 3.22.1 → 3.22.3

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.
@@ -12,25 +12,27 @@ import * as Sentry from '@sentry/node';
12
12
  import {
13
13
  abort,
14
14
  abortIfCancelled,
15
- addSentryCliConfig,
15
+ addDotEnvSentryBuildPluginFile,
16
16
  askShouldCreateExamplePage,
17
17
  confirmContinueIfNoOrDirtyGitRepo,
18
+ createNewConfigFile,
18
19
  ensurePackageIsInstalled,
19
20
  getOrAskForProjectData,
20
21
  getPackageDotJson,
21
22
  installPackage,
22
23
  isUsingTypeScript,
23
24
  printWelcome,
25
+ showCopyPasteInstructions,
24
26
  } from '../utils/clack-utils';
25
27
  import { SentryProjectData, WizardOptions } from '../utils/types';
26
28
  import {
27
29
  getFullUnderscoreErrorCopyPasteSnippet,
28
30
  getGlobalErrorCopyPasteSnippet,
31
+ getInstrumentationHookContent,
32
+ getInstrumentationHookCopyPasteSnippet,
29
33
  getNextjsConfigCjsAppendix,
30
34
  getNextjsConfigCjsTemplate,
31
35
  getNextjsConfigEsmCopyPasteSnippet,
32
- getNextjsSentryBuildOptionsTemplate,
33
- getNextjsWebpackPluginOptionsTemplate,
34
36
  getSentryConfigContents,
35
37
  getSentryDefaultGlobalErrorPage,
36
38
  getSentryDefaultUnderscoreErrorPage,
@@ -38,6 +40,7 @@ import {
38
40
  getSentryExampleAppDirApiRoute,
39
41
  getSentryExamplePageContents,
40
42
  getSimpleUnderscoreErrorCopyPasteSnippet,
43
+ getWithSentryConfigOptionsTemplate,
41
44
  } from './templates';
42
45
  import { traceStep, withTelemetry } from '../telemetry';
43
46
  import { getPackageVersion, hasPackageInstalled } from '../utils/package-json';
@@ -82,7 +85,7 @@ export async function runNextjsWizardWithTelemetry(
82
85
  Sentry.setTag('sdk-already-installed', sdkAlreadyInstalled);
83
86
 
84
87
  await installPackage({
85
- packageName: '@sentry/nextjs@^7.105.0',
88
+ packageName: '@sentry/nextjs@^8',
86
89
  alreadyInstalled: !!packageJson?.dependencies?.['@sentry/nextjs'],
87
90
  });
88
91
 
@@ -248,7 +251,7 @@ export async function runNextjsWizardWithTelemetry(
248
251
  clack.log.info(
249
252
  `It seems like you already have a custom error page for your app directory.\n\nPlease add the following code to your custom error page\nat ${chalk.cyan(
250
253
  path.join(...appDirLocation, globalErrorPageFile),
251
- )}:`,
254
+ )}:\n`,
252
255
  );
253
256
 
254
257
  // eslint-disable-next-line no-console
@@ -282,7 +285,7 @@ export async function runNextjsWizardWithTelemetry(
282
285
  );
283
286
  }
284
287
 
285
- await addSentryCliConfig({ authToken });
288
+ await addDotEnvSentryBuildPluginFile(authToken);
286
289
 
287
290
  const mightBeUsingVercel = fs.existsSync(
288
291
  path.join(process.cwd(), 'vercel.json'),
@@ -390,23 +393,75 @@ async function createOrMergeNextJsFiles(
390
393
  });
391
394
  }
392
395
 
393
- const sentryWebpackOptionsTemplate = getNextjsWebpackPluginOptionsTemplate(
394
- selectedProject.organization.slug,
395
- selectedProject.slug,
396
- selfHosted,
397
- sentryUrl,
398
- );
396
+ await traceStep('setup-instrumentation-hook', async () => {
397
+ const srcInstrumentationTsExists = fs.existsSync(
398
+ path.join(process.cwd(), 'src', 'instrumentation.ts'),
399
+ );
400
+ const srcInstrumentationJsExists = fs.existsSync(
401
+ path.join(process.cwd(), 'src', 'instrumentation.js'),
402
+ );
403
+ const instrumentationTsExists = fs.existsSync(
404
+ path.join(process.cwd(), 'instrumentation.ts'),
405
+ );
406
+ const instrumentationJsExists = fs.existsSync(
407
+ path.join(process.cwd(), 'instrumentation.js'),
408
+ );
409
+
410
+ let instrumentationHookLocation: 'src' | 'root' | 'does-not-exist';
411
+ if (srcInstrumentationTsExists || srcInstrumentationJsExists) {
412
+ instrumentationHookLocation = 'src';
413
+ } else if (instrumentationTsExists || instrumentationJsExists) {
414
+ instrumentationHookLocation = 'root';
415
+ } else {
416
+ instrumentationHookLocation = 'does-not-exist';
417
+ }
399
418
 
400
- const { tunnelRoute } = sdkConfigOptions;
419
+ if (instrumentationHookLocation === 'does-not-exist') {
420
+ const srcFolderExists = fs.existsSync(path.join(process.cwd(), 'src'));
401
421
 
402
- const sentryBuildOptionsTemplate = getNextjsSentryBuildOptionsTemplate({
403
- tunnelRoute,
404
- });
422
+ const instrumentationHookPath = srcFolderExists
423
+ ? path.join(process.cwd(), 'src', 'instrumentation.ts')
424
+ : path.join(process.cwd(), 'instrumentation.ts');
405
425
 
406
- const nextConfigJs = 'next.config.js';
407
- const nextConfigMjs = 'next.config.mjs';
426
+ const successfullyCreated = await createNewConfigFile(
427
+ instrumentationHookPath,
428
+ getInstrumentationHookContent(srcFolderExists ? 'src' : 'root'),
429
+ );
430
+
431
+ if (!successfullyCreated) {
432
+ await showCopyPasteInstructions(
433
+ 'instrumentation.ts',
434
+ getInstrumentationHookCopyPasteSnippet(
435
+ srcFolderExists ? 'src' : 'root',
436
+ ),
437
+ );
438
+ }
439
+ } else {
440
+ await showCopyPasteInstructions(
441
+ srcInstrumentationTsExists
442
+ ? 'instrumentation.ts'
443
+ : srcInstrumentationJsExists
444
+ ? 'instrumentation.js'
445
+ : instrumentationTsExists
446
+ ? 'instrumentation.ts'
447
+ : 'instrumentation.js',
448
+ getInstrumentationHookCopyPasteSnippet(instrumentationHookLocation),
449
+ );
450
+ }
451
+ });
408
452
 
409
453
  await traceStep('setup-next-config', async () => {
454
+ const withSentryConfigOptionsTemplate = getWithSentryConfigOptionsTemplate({
455
+ orgSlug: selectedProject.organization.slug,
456
+ projectSlug: selectedProject.slug,
457
+ selfHosted,
458
+ url: sentryUrl,
459
+ tunnelRoute: sdkConfigOptions.tunnelRoute,
460
+ });
461
+
462
+ const nextConfigJs = 'next.config.js';
463
+ const nextConfigMjs = 'next.config.mjs';
464
+
410
465
  const nextConfigJsExists = fs.existsSync(
411
466
  path.join(process.cwd(), nextConfigJs),
412
467
  );
@@ -419,10 +474,7 @@ async function createOrMergeNextJsFiles(
419
474
 
420
475
  await fs.promises.writeFile(
421
476
  path.join(process.cwd(), nextConfigJs),
422
- getNextjsConfigCjsTemplate(
423
- sentryWebpackOptionsTemplate,
424
- sentryBuildOptionsTemplate,
425
- ),
477
+ getNextjsConfigCjsTemplate(withSentryConfigOptionsTemplate),
426
478
  { encoding: 'utf8', flag: 'w' },
427
479
  );
428
480
 
@@ -460,10 +512,7 @@ async function createOrMergeNextJsFiles(
460
512
  if (shouldInject) {
461
513
  await fs.promises.appendFile(
462
514
  path.join(process.cwd(), nextConfigJs),
463
- getNextjsConfigCjsAppendix(
464
- sentryWebpackOptionsTemplate,
465
- sentryBuildOptionsTemplate,
466
- ),
515
+ getNextjsConfigCjsAppendix(withSentryConfigOptionsTemplate),
467
516
  'utf8',
468
517
  );
469
518
 
@@ -514,8 +563,7 @@ async function createOrMergeNextJsFiles(
514
563
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
515
564
  mod.exports.default = builders.raw(`withSentryConfig(
516
565
  ${expressionToWrap},
517
- ${sentryWebpackOptionsTemplate},
518
- ${sentryBuildOptionsTemplate}
566
+ ${withSentryConfigOptionsTemplate}
519
567
  )`);
520
568
  const newCode = mod.generate().code;
521
569
 
@@ -550,10 +598,7 @@ async function createOrMergeNextJsFiles(
550
598
 
551
599
  // eslint-disable-next-line no-console
552
600
  console.log(
553
- getNextjsConfigEsmCopyPasteSnippet(
554
- sentryWebpackOptionsTemplate,
555
- sentryBuildOptionsTemplate,
556
- ),
601
+ getNextjsConfigEsmCopyPasteSnippet(withSentryConfigOptionsTemplate),
557
602
  );
558
603
 
559
604
  const shouldContinue = await abortIfCancelled(
@@ -1,39 +1,37 @@
1
1
  import chalk from 'chalk';
2
+ import { makeCodeSnippet } from '../utils/clack-utils';
2
3
 
3
- export function getNextjsWebpackPluginOptionsTemplate(
4
- orgSlug: string,
5
- projectSlug: string,
6
- selfHosted: boolean,
7
- url: string,
8
- ): string {
4
+ type WithSentryConfigOptions = {
5
+ orgSlug: string;
6
+ projectSlug: string;
7
+ selfHosted: boolean;
8
+ url: string;
9
+ tunnelRoute: boolean;
10
+ };
11
+
12
+ export function getWithSentryConfigOptionsTemplate({
13
+ orgSlug,
14
+ projectSlug,
15
+ selfHosted,
16
+ tunnelRoute,
17
+ url,
18
+ }: WithSentryConfigOptions): string {
9
19
  return `{
10
20
  // For all available options, see:
11
21
  // https://github.com/getsentry/sentry-webpack-plugin#options
12
22
 
13
- // Suppresses source map uploading logs during build
14
- silent: true,
15
23
  org: "${orgSlug}",
16
24
  project: "${projectSlug}",${selfHosted ? `\n url: "${url}"` : ''}
17
- }`;
18
- }
19
25
 
20
- type SentryNextjsBuildOptions = {
21
- tunnelRoute: boolean;
22
- };
26
+ // Only print logs for uploading source maps in CI
27
+ silent: !process.env.CI,
23
28
 
24
- export function getNextjsSentryBuildOptionsTemplate({
25
- tunnelRoute,
26
- }: SentryNextjsBuildOptions): string {
27
- return `{
28
29
  // For all available options, see:
29
30
  // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
30
31
 
31
32
  // Upload a larger set of source maps for prettier stack traces (increases build time)
32
33
  widenClientFileUpload: true,
33
34
 
34
- // Transpiles SDK to be compatible with IE11 (increases bundle size)
35
- transpileClientSDK: true,
36
-
37
35
  // ${
38
36
  tunnelRoute ? 'Route' : 'Uncomment to route'
39
37
  } browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
@@ -48,7 +46,7 @@ export function getNextjsSentryBuildOptionsTemplate({
48
46
  // Automatically tree-shake Sentry logger statements to reduce bundle size
49
47
  disableLogger: true,
50
48
 
51
- // Enables automatic instrumentation of Vercel Cron Monitors.
49
+ // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
52
50
  // See the following for more information:
53
51
  // https://docs.sentry.io/product/crons/
54
52
  // https://vercel.com/docs/cron-jobs
@@ -57,8 +55,7 @@ export function getNextjsSentryBuildOptionsTemplate({
57
55
  }
58
56
 
59
57
  export function getNextjsConfigCjsTemplate(
60
- sentryWebpackPluginOptionsTemplate: string,
61
- sentryBuildOptionsTemplate: string,
58
+ withSentryConfigOptionsTemplate: string,
62
59
  ): string {
63
60
  return `const { withSentryConfig } = require("@sentry/nextjs");
64
61
 
@@ -67,15 +64,13 @@ const nextConfig = {};
67
64
 
68
65
  module.exports = withSentryConfig(
69
66
  nextConfig,
70
- ${sentryWebpackPluginOptionsTemplate},
71
- ${sentryBuildOptionsTemplate}
67
+ ${withSentryConfigOptionsTemplate}
72
68
  );
73
69
  `;
74
70
  }
75
71
 
76
72
  export function getNextjsConfigCjsAppendix(
77
- sentryWebpackPluginOptionsTemplate: string,
78
- sentryBuildOptionsTemplate: string,
73
+ withSentryConfigOptionsTemplate: string,
79
74
  ): string {
80
75
  return `
81
76
 
@@ -85,15 +80,13 @@ const { withSentryConfig } = require("@sentry/nextjs");
85
80
 
86
81
  module.exports = withSentryConfig(
87
82
  module.exports,
88
- ${sentryWebpackPluginOptionsTemplate},
89
- ${sentryBuildOptionsTemplate}
83
+ ${withSentryConfigOptionsTemplate}
90
84
  );
91
85
  `;
92
86
  }
93
87
 
94
88
  export function getNextjsConfigEsmCopyPasteSnippet(
95
- sentryWebpackPluginOptionsTemplate: string,
96
- sentryBuildOptionsTemplate: string,
89
+ withSentryConfigOptionsTemplate: string,
97
90
  ): string {
98
91
  return `
99
92
 
@@ -102,8 +95,7 @@ import { withSentryConfig } from "@sentry/nextjs";
102
95
 
103
96
  export default withSentryConfig(
104
97
  yourNextConfig,
105
- ${sentryWebpackPluginOptionsTemplate},
106
- ${sentryBuildOptionsTemplate}
98
+ ${withSentryConfigOptionsTemplate}
107
99
  );
108
100
  `;
109
101
  }
@@ -152,7 +144,7 @@ export function getSentryConfigContents(
152
144
  if (config === 'server') {
153
145
  spotlightOption = `
154
146
 
155
- // uncomment the line below to enable Spotlight (https://spotlightjs.com)
147
+ // Uncomment the line below to enable Spotlight (https://spotlightjs.com)
156
148
  // spotlight: process.env.NODE_ENV === 'development',
157
149
  `;
158
150
  }
@@ -235,8 +227,8 @@ export default function Page() {
235
227
  fontSize: "14px",
236
228
  margin: "18px",
237
229
  }}
238
- onClick={() => {
239
- Sentry.startSpan({
230
+ onClick={async () => {
231
+ await Sentry.startSpan({
240
232
  name: 'Example Frontend Span',
241
233
  op: 'test'
242
234
  }, async () => {
@@ -344,6 +336,45 @@ YourCustomErrorComponent.getInitialProps = async (contextData${
344
336
  `;
345
337
  }
346
338
 
339
+ export function getInstrumentationHookContent(
340
+ instrumentationHookLocation: 'src' | 'root',
341
+ ) {
342
+ return `export async function register() {
343
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
344
+ await import('${
345
+ instrumentationHookLocation === 'root' ? '.' : '..'
346
+ }/sentry.server.config');
347
+ }
348
+
349
+ if (process.env.NEXT_RUNTIME === 'edge') {
350
+ await import('${
351
+ instrumentationHookLocation === 'root' ? '.' : '..'
352
+ }/sentry.edge.config');
353
+ }
354
+ }
355
+ `;
356
+ }
357
+
358
+ export function getInstrumentationHookCopyPasteSnippet(
359
+ instrumentationHookLocation: 'src' | 'root',
360
+ ) {
361
+ return makeCodeSnippet(true, (unchanged, plus) => {
362
+ return unchanged(`export ${plus('async')} function register() {
363
+ ${plus(`if (process.env.NEXT_RUNTIME === 'nodejs') {
364
+ await import('${
365
+ instrumentationHookLocation === 'root' ? '.' : '..'
366
+ }/sentry.server.config');
367
+ }
368
+
369
+ if (process.env.NEXT_RUNTIME === 'edge') {
370
+ await import('${
371
+ instrumentationHookLocation === 'root' ? '.' : '..'
372
+ }/sentry.edge.config');
373
+ }`)}
374
+ }`);
375
+ });
376
+ }
377
+
347
378
  export function getSentryDefaultGlobalErrorPage() {
348
379
  return `"use client";
349
380
 
@@ -3,7 +3,10 @@ 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, addSentryCliConfig } from '../../utils/clack-utils';
6
+ import {
7
+ abortIfCancelled,
8
+ addDotEnvSentryBuildPluginFile,
9
+ } from '../../utils/clack-utils';
7
10
  import { WizardOptions } from '../../utils/types';
8
11
 
9
12
  import { SourceMapUploadToolConfigurationOptions } from './types';
@@ -99,7 +102,7 @@ In case you already tried the wizard, we can also show you how to configure your
99
102
  );
100
103
 
101
104
  await traceStep('nextjs-manual-sentryclirc', () =>
102
- addSentryCliConfig({ authToken: options.authToken }),
105
+ addDotEnvSentryBuildPluginFile(options.authToken),
103
106
  );
104
107
  }
105
108
 
@@ -550,7 +550,7 @@ export async function addDotEnvSentryBuildPluginFile(
550
550
  # The SENTRY_AUTH_TOKEN variable is picked up by the Sentry Build Plugin.
551
551
  # It's used for authentication when uploading source maps.
552
552
  # You can also set this env variable in your own \`.env\` files and remove this file.
553
- SENTRY_AUTH_TOKEN="${authToken}"
553
+ SENTRY_AUTH_TOKEN=${authToken}
554
554
  `;
555
555
 
556
556
  const dotEnvFilePath = path.join(process.cwd(), SENTRY_DOT_ENV_FILE);
@@ -800,7 +800,7 @@ ${chalk.cyan('https://github.com/getsentry/sentry-wizard/issues')}`);
800
800
  clack.log.info(`In the meantime, we'll add a dummy auth token (${chalk.cyan(
801
801
  `"${DUMMY_AUTH_TOKEN}"`,
802
802
  )}) for you to replace later.
803
- Create your auth token here:
803
+ Create your auth token here:
804
804
  ${chalk.cyan(
805
805
  selfHosted
806
806
  ? `${sentryUrl}organizations/${selectedProject.organization.slug}/settings/auth-tokens`