@sentry/wizard 3.25.1 → 3.26.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 (75) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin.ts +10 -2
  3. package/dist/bin.js +12 -3
  4. package/dist/bin.js.map +1 -1
  5. package/dist/lib/Helper/Env.js +1 -0
  6. package/dist/lib/Helper/Env.js.map +1 -1
  7. package/dist/lib/Setup.d.ts +6 -0
  8. package/dist/lib/Setup.js +6 -0
  9. package/dist/lib/Setup.js.map +1 -1
  10. package/dist/lib/Steps/ChooseIntegration.js +0 -29
  11. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  12. package/dist/lib/Steps/OpenSentry.js +1 -1
  13. package/dist/lib/Steps/OpenSentry.js.map +1 -1
  14. package/dist/package.json +2 -1
  15. package/dist/src/nextjs/nextjs-wizard.d.ts +1 -1
  16. package/dist/src/nextjs/nextjs-wizard.js +166 -84
  17. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  18. package/dist/src/nextjs/templates.d.ts +3 -2
  19. package/dist/src/nextjs/templates.js +10 -8
  20. package/dist/src/nextjs/templates.js.map +1 -1
  21. package/dist/src/run.d.ts +16 -0
  22. package/dist/src/run.js +195 -0
  23. package/dist/src/run.js.map +1 -0
  24. package/dist/src/telemetry.js +10 -3
  25. package/dist/src/telemetry.js.map +1 -1
  26. package/dist/src/utils/clack-utils.js +35 -11
  27. package/dist/src/utils/clack-utils.js.map +1 -1
  28. package/dist/src/utils/package-json.d.ts +1 -0
  29. package/dist/src/utils/package-json.js.map +1 -1
  30. package/dist/src/utils/package-manager.d.ts +1 -1
  31. package/dist/src/utils/package-manager.js +5 -52
  32. package/dist/src/utils/package-manager.js.map +1 -1
  33. package/dist/test/nextjs/templates.test.js +14 -0
  34. package/dist/test/nextjs/templates.test.js.map +1 -1
  35. package/lib/Helper/Env.ts +1 -0
  36. package/lib/Setup.ts +6 -0
  37. package/lib/Steps/ChooseIntegration.ts +0 -29
  38. package/lib/Steps/OpenSentry.ts +1 -1
  39. package/package.json +2 -1
  40. package/src/nextjs/nextjs-wizard.ts +188 -99
  41. package/src/nextjs/templates.ts +19 -4
  42. package/src/run.ts +142 -0
  43. package/src/telemetry.ts +7 -1
  44. package/src/utils/clack-utils.ts +44 -7
  45. package/src/utils/package-json.ts +1 -0
  46. package/src/utils/package-manager.ts +5 -9
  47. package/test/nextjs/templates.test.ts +56 -0
  48. package/dist/lib/Steps/Integrations/Android.d.ts +0 -9
  49. package/dist/lib/Steps/Integrations/Android.js +0 -86
  50. package/dist/lib/Steps/Integrations/Android.js.map +0 -1
  51. package/dist/lib/Steps/Integrations/Apple.d.ts +0 -10
  52. package/dist/lib/Steps/Integrations/Apple.js +0 -92
  53. package/dist/lib/Steps/Integrations/Apple.js.map +0 -1
  54. package/dist/lib/Steps/Integrations/NextJsShim.d.ts +0 -13
  55. package/dist/lib/Steps/Integrations/NextJsShim.js +0 -99
  56. package/dist/lib/Steps/Integrations/NextJsShim.js.map +0 -1
  57. package/dist/lib/Steps/Integrations/ReactNative.d.ts +0 -10
  58. package/dist/lib/Steps/Integrations/ReactNative.js +0 -93
  59. package/dist/lib/Steps/Integrations/ReactNative.js.map +0 -1
  60. package/dist/lib/Steps/Integrations/Remix.d.ts +0 -12
  61. package/dist/lib/Steps/Integrations/Remix.js +0 -98
  62. package/dist/lib/Steps/Integrations/Remix.js.map +0 -1
  63. package/dist/lib/Steps/Integrations/SourceMapsShim.d.ts +0 -13
  64. package/dist/lib/Steps/Integrations/SourceMapsShim.js +0 -94
  65. package/dist/lib/Steps/Integrations/SourceMapsShim.js.map +0 -1
  66. package/dist/lib/Steps/Integrations/SvelteKitShim.d.ts +0 -13
  67. package/dist/lib/Steps/Integrations/SvelteKitShim.js +0 -99
  68. package/dist/lib/Steps/Integrations/SvelteKitShim.js.map +0 -1
  69. package/lib/Steps/Integrations/Android.ts +0 -23
  70. package/lib/Steps/Integrations/Apple.ts +0 -27
  71. package/lib/Steps/Integrations/NextJsShim.ts +0 -33
  72. package/lib/Steps/Integrations/ReactNative.ts +0 -28
  73. package/lib/Steps/Integrations/Remix.ts +0 -32
  74. package/lib/Steps/Integrations/SourceMapsShim.ts +0 -28
  75. package/lib/Steps/Integrations/SvelteKitShim.ts +0 -33
@@ -10,14 +10,7 @@ import {
10
10
  import { BaseStep } from './BaseStep';
11
11
  import { Cordova } from './Integrations/Cordova';
12
12
  import { Electron } from './Integrations/Electron';
13
- import { NextJsShim } from './Integrations/NextJsShim';
14
- import { ReactNative } from './Integrations/ReactNative';
15
- import { SourceMapsShim } from './Integrations/SourceMapsShim';
16
- import { Apple } from './Integrations/Apple';
17
- import { SvelteKitShim } from './Integrations/SvelteKitShim';
18
13
  import { hasPackageInstalled } from '../../src/utils/package-json';
19
- import { Remix } from './Integrations/Remix';
20
- import { Android } from './Integrations/Android';
21
14
  import { dim } from '../Helper/Logging';
22
15
 
23
16
  let projectPackage: any = {};
@@ -39,34 +32,12 @@ export class ChooseIntegration extends BaseStep {
39
32
 
40
33
  let integration = null;
41
34
  switch (integrationPrompt.integration) {
42
- case Integration.android:
43
- integration = new Android(this._argv);
44
- break;
45
35
  case Integration.cordova:
46
36
  integration = new Cordova(sanitizeUrl(this._argv));
47
37
  break;
48
38
  case Integration.electron:
49
39
  integration = new Electron(sanitizeUrl(this._argv));
50
40
  break;
51
- case Integration.nextjs:
52
- integration = new NextJsShim(this._argv);
53
- break;
54
- case Integration.remix:
55
- integration = new Remix(this._argv);
56
- break;
57
- case Integration.sveltekit:
58
- integration = new SvelteKitShim(this._argv);
59
- break;
60
- case Integration.sourcemaps:
61
- integration = new SourceMapsShim(this._argv);
62
- break;
63
- case Integration.ios:
64
- integration = new Apple(this._argv);
65
- break;
66
- case Integration.reactNative:
67
- default:
68
- integration = new ReactNative(this._argv);
69
- break;
70
41
  }
71
42
 
72
43
  return { integration };
@@ -48,7 +48,7 @@ export class OpenSentry extends BaseStep {
48
48
 
49
49
  const urlToOpen = urlObj.toString();
50
50
 
51
- opn(urlToOpen).catch(() => {
51
+ opn(urlToOpen, { wait: false }).catch(() => {
52
52
  // opn throws in environments that don't have a browser (e.g. remote shells) so we just noop here
53
53
  });
54
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/wizard",
3
- "version": "3.25.1",
3
+ "version": "3.26.0",
4
4
  "homepage": "https://github.com/getsentry/sentry-wizard",
5
5
  "repository": "https://github.com/getsentry/sentry-wizard",
6
6
  "description": "Sentry wizard helping you to configure your project",
@@ -50,6 +50,7 @@
50
50
  "@types/node": "^10.11.0",
51
51
  "@types/rimraf": "^3.0.2",
52
52
  "@types/semver": "^7.3.7",
53
+ "@types/yargs": "^16.0.9",
53
54
  "@typescript-eslint/eslint-plugin": "^5.13.0",
54
55
  "@typescript-eslint/parser": "^5.13.0",
55
56
  "eslint": "^8.18.0",
@@ -24,7 +24,7 @@ import {
24
24
  printWelcome,
25
25
  showCopyPasteInstructions,
26
26
  } from '../utils/clack-utils';
27
- import { SentryProjectData, WizardOptions } from '../utils/types';
27
+ import type { SentryProjectData, WizardOptions } from '../utils/types';
28
28
  import {
29
29
  getFullUnderscoreErrorCopyPasteSnippet,
30
30
  getGlobalErrorCopyPasteSnippet,
@@ -36,7 +36,7 @@ import {
36
36
  getSentryConfigContents,
37
37
  getSentryDefaultGlobalErrorPage,
38
38
  getSentryDefaultUnderscoreErrorPage,
39
- getSentryExampleApiRoute,
39
+ getSentryExamplePagesDirApiRoute,
40
40
  getSentryExampleAppDirApiRoute,
41
41
  getSentryExamplePageContents,
42
42
  getSimpleUnderscoreErrorCopyPasteSnippet,
@@ -93,9 +93,12 @@ export async function runNextjsWizardWithTelemetry(
93
93
 
94
94
  await traceStep('configure-sdk', async () => {
95
95
  const tunnelRoute = await askShouldSetTunnelRoute();
96
+ const reactComponentAnnotation =
97
+ await askShouldEnableReactComponentAnnotation();
96
98
 
97
99
  await createOrMergeNextJsFiles(selectedProject, selfHosted, sentryUrl, {
98
100
  tunnelRoute,
101
+ reactComponentAnnotation,
99
102
  });
100
103
  });
101
104
 
@@ -321,6 +324,7 @@ ${chalk.dim(
321
324
 
322
325
  type SDKConfigOptions = {
323
326
  tunnelRoute: boolean;
327
+ reactComponentAnnotation: boolean;
324
328
  };
325
329
 
326
330
  async function createOrMergeNextJsFiles(
@@ -400,60 +404,85 @@ async function createOrMergeNextJsFiles(
400
404
  }
401
405
 
402
406
  await traceStep('setup-instrumentation-hook', async () => {
403
- const srcInstrumentationTsExists = fs.existsSync(
404
- path.join(process.cwd(), 'src', 'instrumentation.ts'),
405
- );
406
- const srcInstrumentationJsExists = fs.existsSync(
407
- path.join(process.cwd(), 'src', 'instrumentation.js'),
408
- );
407
+ const hasRootAppDirectory = hasDirectoryPathFromRoot('app');
408
+ const hasRootPagesDirectory = hasDirectoryPathFromRoot('pages');
409
+ const hasSrcDirectory = hasDirectoryPathFromRoot('src');
410
+
411
+ let instrumentationHookLocation: 'src' | 'root' | 'does-not-exist';
412
+
409
413
  const instrumentationTsExists = fs.existsSync(
410
414
  path.join(process.cwd(), 'instrumentation.ts'),
411
415
  );
412
416
  const instrumentationJsExists = fs.existsSync(
413
417
  path.join(process.cwd(), 'instrumentation.js'),
414
418
  );
419
+ const srcInstrumentationTsExists = fs.existsSync(
420
+ path.join(process.cwd(), 'src', 'instrumentation.ts'),
421
+ );
422
+ const srcInstrumentationJsExists = fs.existsSync(
423
+ path.join(process.cwd(), 'src', 'instrumentation.js'),
424
+ );
415
425
 
416
- let instrumentationHookLocation: 'src' | 'root' | 'does-not-exist';
417
- if (srcInstrumentationTsExists || srcInstrumentationJsExists) {
418
- instrumentationHookLocation = 'src';
419
- } else if (instrumentationTsExists || instrumentationJsExists) {
420
- instrumentationHookLocation = 'root';
426
+ // https://nextjs.org/docs/app/building-your-application/configuring/src-directory
427
+ // https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation
428
+ // The logic for where Next.js picks up the instrumentation file is as follows:
429
+ // - If there is either an `app` folder or a `pages` folder in the root directory of your Next.js app, Next.js looks
430
+ // for an `instrumentation.ts` file in the root of the Next.js app.
431
+ // - Otherwise, if there is neither an `app` folder or a `pages` folder in the rood directory of your Next.js app,
432
+ // AND if there is an `src` folder, Next.js will look for the `instrumentation.ts` file in the `src` folder.
433
+ if (hasRootPagesDirectory || hasRootAppDirectory) {
434
+ if (instrumentationJsExists || instrumentationTsExists) {
435
+ instrumentationHookLocation = 'root';
436
+ } else {
437
+ instrumentationHookLocation = 'does-not-exist';
438
+ }
421
439
  } else {
422
- instrumentationHookLocation = 'does-not-exist';
440
+ if (srcInstrumentationTsExists || srcInstrumentationJsExists) {
441
+ instrumentationHookLocation = 'src';
442
+ } else {
443
+ instrumentationHookLocation = 'does-not-exist';
444
+ }
423
445
  }
424
446
 
447
+ const newInstrumentationFileName = `instrumentation.${
448
+ typeScriptDetected ? 'ts' : 'js'
449
+ }`;
450
+
425
451
  if (instrumentationHookLocation === 'does-not-exist') {
426
- const newInstrumentationFileName = `instrumentation.${
427
- typeScriptDetected ? 'ts' : 'js'
428
- }`;
429
- const srcFolderExists = fs.existsSync(path.join(process.cwd(), 'src'));
452
+ let newInstrumentationHookLocation: 'root' | 'src';
453
+ if (hasRootPagesDirectory || hasRootAppDirectory) {
454
+ newInstrumentationHookLocation = 'root';
455
+ } else if (hasSrcDirectory) {
456
+ newInstrumentationHookLocation = 'src';
457
+ } else {
458
+ newInstrumentationHookLocation = 'root';
459
+ }
430
460
 
431
- const instrumentationHookPath = srcFolderExists
432
- ? path.join(process.cwd(), 'src', newInstrumentationFileName)
433
- : path.join(process.cwd(), newInstrumentationFileName);
461
+ const newInstrumentationHookPath =
462
+ newInstrumentationHookLocation === 'root'
463
+ ? path.join(process.cwd(), newInstrumentationFileName)
464
+ : path.join(process.cwd(), 'src', newInstrumentationFileName);
434
465
 
435
466
  const successfullyCreated = await createNewConfigFile(
436
- instrumentationHookPath,
437
- getInstrumentationHookContent(srcFolderExists ? 'src' : 'root'),
467
+ newInstrumentationHookPath,
468
+ getInstrumentationHookContent(newInstrumentationHookLocation),
438
469
  );
439
470
 
440
471
  if (!successfullyCreated) {
441
472
  await showCopyPasteInstructions(
442
473
  newInstrumentationFileName,
443
474
  getInstrumentationHookCopyPasteSnippet(
444
- srcFolderExists ? 'src' : 'root',
475
+ newInstrumentationHookLocation,
445
476
  ),
446
477
  );
447
478
  }
448
479
  } else {
449
480
  await showCopyPasteInstructions(
450
- srcInstrumentationTsExists
481
+ srcInstrumentationTsExists || instrumentationTsExists
451
482
  ? 'instrumentation.ts'
452
- : srcInstrumentationJsExists
483
+ : srcInstrumentationJsExists || instrumentationJsExists
453
484
  ? 'instrumentation.js'
454
- : instrumentationTsExists
455
- ? 'instrumentation.ts'
456
- : 'instrumentation.js',
485
+ : newInstrumentationFileName,
457
486
  getInstrumentationHookCopyPasteSnippet(instrumentationHookLocation),
458
487
  );
459
488
  }
@@ -466,23 +495,28 @@ async function createOrMergeNextJsFiles(
466
495
  selfHosted,
467
496
  sentryUrl,
468
497
  tunnelRoute: sdkConfigOptions.tunnelRoute,
498
+ reactComponentAnnotation: sdkConfigOptions.reactComponentAnnotation,
469
499
  });
470
500
 
471
- const nextConfigJs = 'next.config.js';
472
- const nextConfigMjs = 'next.config.mjs';
473
-
474
- const nextConfigJsExists = fs.existsSync(
475
- path.join(process.cwd(), nextConfigJs),
476
- );
477
- const nextConfigMjsExists = fs.existsSync(
478
- path.join(process.cwd(), nextConfigMjs),
501
+ const nextConfigPossibleFilesMap = {
502
+ js: 'next.config.js',
503
+ mjs: 'next.config.mjs',
504
+ cjs: 'next.config.cjs',
505
+ ts: 'next.config.ts',
506
+ mts: 'next.config.mts',
507
+ cts: 'next.config.cts',
508
+ };
509
+
510
+ const foundNextConfigFile = Object.entries(nextConfigPossibleFilesMap).find(
511
+ ([, fileName]) => fs.existsSync(path.join(process.cwd(), fileName)),
479
512
  );
480
513
 
481
- if (!nextConfigJsExists && !nextConfigMjsExists) {
514
+ if (!foundNextConfigFile) {
482
515
  Sentry.setTag('next-config-strategy', 'create');
483
516
 
484
517
  await fs.promises.writeFile(
485
- path.join(process.cwd(), nextConfigJs),
518
+ // We are creating a `next.config.js` file by default as it is supported by the most Next.js versions
519
+ path.join(process.cwd(), nextConfigPossibleFilesMap.js),
486
520
  getNextjsConfigCjsTemplate(withSentryConfigOptionsTemplate),
487
521
  { encoding: 'utf8', flag: 'w' },
488
522
  );
@@ -490,19 +524,24 @@ async function createOrMergeNextJsFiles(
490
524
  clack.log.success(
491
525
  `Created ${chalk.cyan('next.config.js')} with Sentry configuration.`,
492
526
  );
527
+
528
+ return;
493
529
  }
494
530
 
495
- if (nextConfigJsExists) {
531
+ const [foundNextConfigFileType, foundNextConfigFileFilename] =
532
+ foundNextConfigFile;
533
+
534
+ if (foundNextConfigFileType === 'js' || foundNextConfigFileType === 'cjs') {
496
535
  Sentry.setTag('next-config-strategy', 'modify');
497
536
 
498
- const nextConfigJsContent = fs.readFileSync(
499
- path.join(process.cwd(), nextConfigJs),
537
+ const nextConfigCjsContent = fs.readFileSync(
538
+ path.join(process.cwd(), foundNextConfigFileFilename),
500
539
  'utf8',
501
540
  );
502
541
 
503
542
  const probablyIncludesSdk =
504
- nextConfigJsContent.includes('@sentry/nextjs') &&
505
- nextConfigJsContent.includes('withSentryConfig');
543
+ nextConfigCjsContent.includes('@sentry/nextjs') &&
544
+ nextConfigCjsContent.includes('withSentryConfig');
506
545
 
507
546
  let shouldInject = true;
508
547
 
@@ -510,7 +549,7 @@ async function createOrMergeNextJsFiles(
510
549
  const injectAnyhow = await abortIfCancelled(
511
550
  clack.confirm({
512
551
  message: `${chalk.cyan(
513
- nextConfigJs,
552
+ foundNextConfigFileFilename,
514
553
  )} already contains Sentry SDK configuration. Should the wizard modify it anyways?`,
515
554
  }),
516
555
  );
@@ -520,14 +559,14 @@ async function createOrMergeNextJsFiles(
520
559
 
521
560
  if (shouldInject) {
522
561
  await fs.promises.appendFile(
523
- path.join(process.cwd(), nextConfigJs),
562
+ path.join(process.cwd(), foundNextConfigFileFilename),
524
563
  getNextjsConfigCjsAppendix(withSentryConfigOptionsTemplate),
525
564
  'utf8',
526
565
  );
527
566
 
528
567
  clack.log.success(
529
568
  `Added Sentry configuration to ${chalk.cyan(
530
- nextConfigJs,
569
+ foundNextConfigFileFilename,
531
570
  )}. ${chalk.dim('(you probably want to clean this up a bit!)')}`,
532
571
  );
533
572
  }
@@ -535,9 +574,14 @@ async function createOrMergeNextJsFiles(
535
574
  Sentry.setTag('next-config-mod-result', 'success');
536
575
  }
537
576
 
538
- if (nextConfigMjsExists) {
577
+ if (
578
+ foundNextConfigFileType === 'mjs' ||
579
+ foundNextConfigFileType === 'mts' ||
580
+ foundNextConfigFileType === 'cts' ||
581
+ foundNextConfigFileType === 'ts'
582
+ ) {
539
583
  const nextConfigMjsContent = fs.readFileSync(
540
- path.join(process.cwd(), nextConfigMjs),
584
+ path.join(process.cwd(), foundNextConfigFileFilename),
541
585
  'utf8',
542
586
  );
543
587
 
@@ -551,7 +595,7 @@ async function createOrMergeNextJsFiles(
551
595
  const injectAnyhow = await abortIfCancelled(
552
596
  clack.confirm({
553
597
  message: `${chalk.cyan(
554
- nextConfigMjs,
598
+ foundNextConfigFileFilename,
555
599
  )} already contains Sentry SDK configuration. Should the wizard modify it anyways?`,
556
600
  }),
557
601
  );
@@ -577,7 +621,7 @@ async function createOrMergeNextJsFiles(
577
621
  const newCode = mod.generate().code;
578
622
 
579
623
  await fs.promises.writeFile(
580
- path.join(process.cwd(), nextConfigMjs),
624
+ path.join(process.cwd(), foundNextConfigFileFilename),
581
625
  newCode,
582
626
  {
583
627
  encoding: 'utf8',
@@ -586,7 +630,7 @@ async function createOrMergeNextJsFiles(
586
630
  );
587
631
  clack.log.success(
588
632
  `Added Sentry configuration to ${chalk.cyan(
589
- nextConfigMjs,
633
+ foundNextConfigFileFilename,
590
634
  )}. ${chalk.dim('(you probably want to clean this up a bit!)')}`,
591
635
  );
592
636
 
@@ -596,12 +640,14 @@ async function createOrMergeNextJsFiles(
596
640
  Sentry.setTag('next-config-mod-result', 'fail');
597
641
  clack.log.warn(
598
642
  chalk.yellow(
599
- `Something went wrong writing to ${chalk.cyan(nextConfigMjs)}`,
643
+ `Something went wrong writing to ${chalk.cyan(
644
+ foundNextConfigFileFilename,
645
+ )}.`,
600
646
  ),
601
647
  );
602
648
  clack.log.info(
603
649
  `Please put the following code snippet into ${chalk.cyan(
604
- nextConfigMjs,
650
+ foundNextConfigFileFilename,
605
651
  )}: ${chalk.dim('You probably have to clean it up a bit.')}\n`,
606
652
  );
607
653
 
@@ -613,7 +659,7 @@ async function createOrMergeNextJsFiles(
613
659
  const shouldContinue = await abortIfCancelled(
614
660
  clack.confirm({
615
661
  message: `Are you done putting the snippet above into ${chalk.cyan(
616
- nextConfigMjs,
662
+ foundNextConfigFileFilename,
617
663
  )}?`,
618
664
  active: 'Yes',
619
665
  inactive: 'No, get me out of here',
@@ -628,50 +674,58 @@ async function createOrMergeNextJsFiles(
628
674
  });
629
675
  }
630
676
 
677
+ function hasDirectoryPathFromRoot(dirnameOrDirs: string | string[]): boolean {
678
+ const dirPath = Array.isArray(dirnameOrDirs)
679
+ ? path.join(process.cwd(), ...dirnameOrDirs)
680
+ : path.join(process.cwd(), dirnameOrDirs);
681
+
682
+ return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
683
+ }
684
+
631
685
  async function createExamplePage(
632
686
  selfHosted: boolean,
633
687
  selectedProject: SentryProjectData,
634
688
  sentryUrl: string,
635
689
  ): Promise<void> {
636
- const srcDir = path.join(process.cwd(), 'src');
637
- const maybePagesDirPath = path.join(process.cwd(), 'pages');
638
- const maybeSrcPagesDirPath = path.join(srcDir, 'pages');
639
- const maybeAppDirPath = path.join(process.cwd(), 'app');
640
- const maybeSrcAppDirPath = path.join(srcDir, 'app');
690
+ const hasSrcDirectory = hasDirectoryPathFromRoot('src');
691
+ const hasRootAppDirectory = hasDirectoryPathFromRoot('app');
692
+ const hasRootPagesDirectory = hasDirectoryPathFromRoot('pages');
693
+ const hasSrcAppDirectory = hasDirectoryPathFromRoot(['src', 'app']);
694
+ const hasSrcPagesDirectory = hasDirectoryPathFromRoot(['src', 'pages']);
695
+
696
+ Sentry.setTag('nextjs-app-dir', hasRootAppDirectory || hasSrcAppDirectory);
641
697
 
642
698
  const typeScriptDetected = isUsingTypeScript();
643
699
 
644
- let pagesLocation =
645
- fs.existsSync(maybePagesDirPath) &&
646
- fs.lstatSync(maybePagesDirPath).isDirectory()
647
- ? ['pages']
648
- : fs.existsSync(maybeSrcPagesDirPath) &&
649
- fs.lstatSync(maybeSrcPagesDirPath).isDirectory()
700
+ // If `pages` or an `app` directory exists in the root, we'll put the example page there.
701
+ // `app` directory takes priority over `pages` directory when they coexist, so we prioritize that.
702
+ // https://nextjs.org/docs/app/building-your-application/routing#the-app-router
703
+
704
+ const appFolderLocation = hasRootAppDirectory
705
+ ? ['app']
706
+ : hasSrcAppDirectory
707
+ ? ['src', 'app']
708
+ : undefined;
709
+
710
+ let pagesFolderLocation = hasRootPagesDirectory
711
+ ? ['pages']
712
+ : hasSrcPagesDirectory
713
+ ? ['src', 'pages']
714
+ : undefined;
715
+
716
+ // If the user has neither pages nor app directory we create a pages folder for them
717
+ if (!appFolderLocation && !pagesFolderLocation) {
718
+ const newPagesFolderLocation = hasSrcDirectory
650
719
  ? ['src', 'pages']
651
- : undefined;
652
-
653
- const appLocation =
654
- fs.existsSync(maybeAppDirPath) &&
655
- fs.lstatSync(maybeAppDirPath).isDirectory()
656
- ? ['app']
657
- : fs.existsSync(maybeSrcAppDirPath) &&
658
- fs.lstatSync(maybeSrcAppDirPath).isDirectory()
659
- ? ['src', 'app']
660
- : undefined;
661
-
662
- if (!pagesLocation && !appLocation) {
663
- pagesLocation =
664
- fs.existsSync(srcDir) && fs.lstatSync(srcDir).isDirectory()
665
- ? ['src', 'pages']
666
- : ['pages'];
667
- fs.mkdirSync(path.join(process.cwd(), ...pagesLocation), {
720
+ : ['pages'];
721
+ fs.mkdirSync(path.join(process.cwd(), ...newPagesFolderLocation), {
668
722
  recursive: true,
669
723
  });
670
- }
671
724
 
672
- Sentry.setTag('nextjs-app-dir', !!appLocation);
725
+ pagesFolderLocation = newPagesFolderLocation;
726
+ }
673
727
 
674
- if (appLocation) {
728
+ if (appFolderLocation) {
675
729
  const examplePageContents = getSentryExamplePageContents({
676
730
  selfHosted,
677
731
  orgSlug: selectedProject.organization.slug,
@@ -681,7 +735,7 @@ async function createExamplePage(
681
735
  });
682
736
 
683
737
  fs.mkdirSync(
684
- path.join(process.cwd(), ...appLocation, 'sentry-example-page'),
738
+ path.join(process.cwd(), ...appFolderLocation, 'sentry-example-page'),
685
739
  {
686
740
  recursive: true,
687
741
  },
@@ -692,7 +746,7 @@ async function createExamplePage(
692
746
  await fs.promises.writeFile(
693
747
  path.join(
694
748
  process.cwd(),
695
- ...appLocation,
749
+ ...appFolderLocation,
696
750
  'sentry-example-page',
697
751
  newPageFileName,
698
752
  ),
@@ -702,12 +756,17 @@ async function createExamplePage(
702
756
 
703
757
  clack.log.success(
704
758
  `Created ${chalk.cyan(
705
- path.join(...appLocation, 'sentry-example-page', newPageFileName),
759
+ path.join(...appFolderLocation, 'sentry-example-page', newPageFileName),
706
760
  )}.`,
707
761
  );
708
762
 
709
763
  fs.mkdirSync(
710
- path.join(process.cwd(), ...appLocation, 'api', 'sentry-example-api'),
764
+ path.join(
765
+ process.cwd(),
766
+ ...appFolderLocation,
767
+ 'api',
768
+ 'sentry-example-api',
769
+ ),
711
770
  {
712
771
  recursive: true,
713
772
  },
@@ -718,7 +777,7 @@ async function createExamplePage(
718
777
  await fs.promises.writeFile(
719
778
  path.join(
720
779
  process.cwd(),
721
- ...appLocation,
780
+ ...appFolderLocation,
722
781
  'api',
723
782
  'sentry-example-api',
724
783
  newRouteFileName,
@@ -730,14 +789,14 @@ async function createExamplePage(
730
789
  clack.log.success(
731
790
  `Created ${chalk.cyan(
732
791
  path.join(
733
- ...appLocation,
792
+ ...appFolderLocation,
734
793
  'api',
735
794
  'sentry-example-api',
736
795
  newRouteFileName,
737
796
  ),
738
797
  )}.`,
739
798
  );
740
- } else if (pagesLocation) {
799
+ } else if (pagesFolderLocation) {
741
800
  const examplePageContents = getSentryExamplePageContents({
742
801
  selfHosted,
743
802
  orgSlug: selectedProject.organization.slug,
@@ -747,35 +806,39 @@ async function createExamplePage(
747
806
  });
748
807
 
749
808
  await fs.promises.writeFile(
750
- path.join(process.cwd(), ...pagesLocation, 'sentry-example-page.jsx'),
809
+ path.join(
810
+ process.cwd(),
811
+ ...pagesFolderLocation,
812
+ 'sentry-example-page.jsx',
813
+ ),
751
814
  examplePageContents,
752
815
  { encoding: 'utf8', flag: 'w' },
753
816
  );
754
817
 
755
818
  clack.log.success(
756
819
  `Created ${chalk.cyan(
757
- path.join(...pagesLocation, 'sentry-example-page.js'),
820
+ path.join(...pagesFolderLocation, 'sentry-example-page.js'),
758
821
  )}.`,
759
822
  );
760
823
 
761
- fs.mkdirSync(path.join(process.cwd(), ...pagesLocation, 'api'), {
824
+ fs.mkdirSync(path.join(process.cwd(), ...pagesFolderLocation, 'api'), {
762
825
  recursive: true,
763
826
  });
764
827
 
765
828
  await fs.promises.writeFile(
766
829
  path.join(
767
830
  process.cwd(),
768
- ...pagesLocation,
831
+ ...pagesFolderLocation,
769
832
  'api',
770
833
  'sentry-example-api.js',
771
834
  ),
772
- getSentryExampleApiRoute(),
835
+ getSentryExamplePagesDirApiRoute(),
773
836
  { encoding: 'utf8', flag: 'w' },
774
837
  );
775
838
 
776
839
  clack.log.success(
777
840
  `Created ${chalk.cyan(
778
- path.join(...pagesLocation, 'api', 'sentry-example-api.js'),
841
+ path.join(...pagesFolderLocation, 'api', 'sentry-example-api.js'),
779
842
  )}.`,
780
843
  );
781
844
  }
@@ -817,3 +880,29 @@ async function askShouldSetTunnelRoute() {
817
880
  return shouldSetTunnelRoute;
818
881
  });
819
882
  }
883
+
884
+ async function askShouldEnableReactComponentAnnotation() {
885
+ return await traceStep('ask-react-component-annotation-option', async () => {
886
+ const shouldEnableReactComponentAnnotation = await abortIfCancelled(
887
+ clack.select({
888
+ message:
889
+ 'Do you want to enable React component annotations to make breadcrumbs and session replays more readable?',
890
+ options: [
891
+ {
892
+ label: 'Yes',
893
+ value: true,
894
+ hint: 'Annotates React component names (increases bundle size)',
895
+ },
896
+ {
897
+ label: 'No',
898
+ value: false,
899
+ hint: 'Continue without React component annotations',
900
+ },
901
+ ],
902
+ initialValue: false,
903
+ }),
904
+ );
905
+
906
+ return shouldEnableReactComponentAnnotation;
907
+ });
908
+ }