@sentry/wizard 3.25.2 → 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.
- package/CHANGELOG.md +10 -0
- package/dist/lib/Steps/OpenSentry.js +1 -1
- package/dist/lib/Steps/OpenSentry.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/nextjs/nextjs-wizard.d.ts +1 -1
- package/dist/src/nextjs/nextjs-wizard.js +166 -84
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/templates.d.ts +3 -2
- package/dist/src/nextjs/templates.js +10 -8
- package/dist/src/nextjs/templates.js.map +1 -1
- package/dist/src/telemetry.js +10 -3
- package/dist/src/telemetry.js.map +1 -1
- package/dist/src/utils/clack-utils.js +34 -10
- package/dist/src/utils/clack-utils.js.map +1 -1
- package/dist/src/utils/package-manager.d.ts +1 -1
- package/dist/src/utils/package-manager.js +5 -52
- package/dist/src/utils/package-manager.js.map +1 -1
- package/dist/test/nextjs/templates.test.js +14 -0
- package/dist/test/nextjs/templates.test.js.map +1 -1
- package/lib/Steps/OpenSentry.ts +1 -1
- package/package.json +1 -1
- package/src/nextjs/nextjs-wizard.ts +188 -99
- package/src/nextjs/templates.ts +19 -4
- package/src/telemetry.ts +7 -1
- package/src/utils/clack-utils.ts +43 -6
- package/src/utils/package-manager.ts +5 -9
- package/test/nextjs/templates.test.ts +56 -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
|
-
|
|
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
|
|
404
|
-
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
437
|
-
getInstrumentationHookContent(
|
|
467
|
+
newInstrumentationHookPath,
|
|
468
|
+
getInstrumentationHookContent(newInstrumentationHookLocation),
|
|
438
469
|
);
|
|
439
470
|
|
|
440
471
|
if (!successfullyCreated) {
|
|
441
472
|
await showCopyPasteInstructions(
|
|
442
473
|
newInstrumentationFileName,
|
|
443
474
|
getInstrumentationHookCopyPasteSnippet(
|
|
444
|
-
|
|
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
|
-
:
|
|
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
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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 (!
|
|
514
|
+
if (!foundNextConfigFile) {
|
|
482
515
|
Sentry.setTag('next-config-strategy', 'create');
|
|
483
516
|
|
|
484
517
|
await fs.promises.writeFile(
|
|
485
|
-
|
|
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
|
-
|
|
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
|
|
499
|
-
path.join(process.cwd(),
|
|
537
|
+
const nextConfigCjsContent = fs.readFileSync(
|
|
538
|
+
path.join(process.cwd(), foundNextConfigFileFilename),
|
|
500
539
|
'utf8',
|
|
501
540
|
);
|
|
502
541
|
|
|
503
542
|
const probablyIncludesSdk =
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
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(),
|
|
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
|
-
|
|
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 (
|
|
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(),
|
|
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
|
-
|
|
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(),
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
637
|
-
const
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
const
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
725
|
+
pagesFolderLocation = newPagesFolderLocation;
|
|
726
|
+
}
|
|
673
727
|
|
|
674
|
-
if (
|
|
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(), ...
|
|
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
|
-
...
|
|
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(...
|
|
759
|
+
path.join(...appFolderLocation, 'sentry-example-page', newPageFileName),
|
|
706
760
|
)}.`,
|
|
707
761
|
);
|
|
708
762
|
|
|
709
763
|
fs.mkdirSync(
|
|
710
|
-
path.join(
|
|
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
|
-
...
|
|
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
|
-
...
|
|
792
|
+
...appFolderLocation,
|
|
734
793
|
'api',
|
|
735
794
|
'sentry-example-api',
|
|
736
795
|
newRouteFileName,
|
|
737
796
|
),
|
|
738
797
|
)}.`,
|
|
739
798
|
);
|
|
740
|
-
} else if (
|
|
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(
|
|
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(...
|
|
820
|
+
path.join(...pagesFolderLocation, 'sentry-example-page.js'),
|
|
758
821
|
)}.`,
|
|
759
822
|
);
|
|
760
823
|
|
|
761
|
-
fs.mkdirSync(path.join(process.cwd(), ...
|
|
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
|
-
...
|
|
831
|
+
...pagesFolderLocation,
|
|
769
832
|
'api',
|
|
770
833
|
'sentry-example-api.js',
|
|
771
834
|
),
|
|
772
|
-
|
|
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(...
|
|
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
|
+
}
|
package/src/nextjs/templates.ts
CHANGED
|
@@ -7,6 +7,7 @@ type WithSentryConfigOptions = {
|
|
|
7
7
|
selfHosted: boolean;
|
|
8
8
|
sentryUrl: string;
|
|
9
9
|
tunnelRoute: boolean;
|
|
10
|
+
reactComponentAnnotation: boolean;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
export function getWithSentryConfigOptionsTemplate({
|
|
@@ -14,6 +15,7 @@ export function getWithSentryConfigOptionsTemplate({
|
|
|
14
15
|
projectSlug,
|
|
15
16
|
selfHosted,
|
|
16
17
|
tunnelRoute,
|
|
18
|
+
reactComponentAnnotation,
|
|
17
19
|
sentryUrl,
|
|
18
20
|
}: WithSentryConfigOptions): string {
|
|
19
21
|
return `{
|
|
@@ -32,7 +34,15 @@ export function getWithSentryConfigOptionsTemplate({
|
|
|
32
34
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
|
33
35
|
|
|
34
36
|
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
|
35
|
-
widenClientFileUpload: true
|
|
37
|
+
widenClientFileUpload: true,${
|
|
38
|
+
reactComponentAnnotation
|
|
39
|
+
? `\n
|
|
40
|
+
// Automatically annotate React components to show their full name in breadcrumbs and session replay
|
|
41
|
+
reactComponentAnnotation: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
},`
|
|
44
|
+
: ''
|
|
45
|
+
}
|
|
36
46
|
|
|
37
47
|
// ${
|
|
38
48
|
tunnelRoute ? 'Route' : 'Uncomment to route'
|
|
@@ -125,7 +135,6 @@ export function getSentryConfigContents(
|
|
|
125
135
|
let additionalOptions = '';
|
|
126
136
|
if (config === 'client') {
|
|
127
137
|
additionalOptions = `
|
|
128
|
-
|
|
129
138
|
replaysOnErrorSampleRate: 1.0,
|
|
130
139
|
|
|
131
140
|
// This sets the sample rate to be 10%. You may want this to be 100% while
|
|
@@ -261,7 +270,7 @@ export default function Page() {
|
|
|
261
270
|
`;
|
|
262
271
|
}
|
|
263
272
|
|
|
264
|
-
export function
|
|
273
|
+
export function getSentryExamplePagesDirApiRoute() {
|
|
265
274
|
return `// A faulty API route to test Sentry's error monitoring
|
|
266
275
|
export default function handler(_req, res) {
|
|
267
276
|
throw new Error("Sentry Example API Route Error");
|
|
@@ -307,16 +316,19 @@ export default CustomErrorComponent;
|
|
|
307
316
|
export function getSimpleUnderscoreErrorCopyPasteSnippet() {
|
|
308
317
|
return `
|
|
309
318
|
${chalk.green(`import * as Sentry from '@sentry/nextjs';`)}
|
|
319
|
+
${chalk.green(`import Error from "next/error";`)}
|
|
310
320
|
|
|
311
321
|
${chalk.dim(
|
|
312
322
|
'// Replace "YourCustomErrorComponent" with your custom error component!',
|
|
313
323
|
)}
|
|
314
324
|
YourCustomErrorComponent.getInitialProps = async (${chalk.green(
|
|
315
|
-
|
|
325
|
+
'contextData',
|
|
316
326
|
)}) => {
|
|
317
327
|
${chalk.green('await Sentry.captureUnderscoreErrorException(contextData);')}
|
|
318
328
|
|
|
319
329
|
${chalk.dim('// ...other getInitialProps code')}
|
|
330
|
+
|
|
331
|
+
return Error.getInitialProps(contextData);
|
|
320
332
|
};
|
|
321
333
|
`;
|
|
322
334
|
}
|
|
@@ -326,6 +338,7 @@ export function getFullUnderscoreErrorCopyPasteSnippet(isTs: boolean) {
|
|
|
326
338
|
import * as Sentry from '@sentry/nextjs';${
|
|
327
339
|
isTs ? '\nimport type { NextPageContext } from "next";' : ''
|
|
328
340
|
}
|
|
341
|
+
import Error from "next/error";
|
|
329
342
|
|
|
330
343
|
${chalk.dim(
|
|
331
344
|
'// Replace "YourCustomErrorComponent" with your custom error component!',
|
|
@@ -334,6 +347,8 @@ YourCustomErrorComponent.getInitialProps = async (contextData${
|
|
|
334
347
|
isTs ? ': NextPageContext' : ''
|
|
335
348
|
}) => {
|
|
336
349
|
await Sentry.captureUnderscoreErrorException(contextData);
|
|
350
|
+
|
|
351
|
+
return Error.getInitialProps(contextData);
|
|
337
352
|
};
|
|
338
353
|
`;
|
|
339
354
|
}
|
package/src/telemetry.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
runWithAsyncContext,
|
|
9
9
|
setTag,
|
|
10
10
|
startSpan,
|
|
11
|
+
flush,
|
|
11
12
|
} from '@sentry/node';
|
|
12
13
|
import packageJson from '../package.json';
|
|
13
14
|
|
|
@@ -49,7 +50,12 @@ export async function withTelemetry<F>(
|
|
|
49
50
|
throw e;
|
|
50
51
|
} finally {
|
|
51
52
|
sentryHub.endSession();
|
|
52
|
-
await sentryClient.flush(3000)
|
|
53
|
+
await sentryClient.flush(3000).then(null, () => {
|
|
54
|
+
// If telemetry flushing fails we generally don't care
|
|
55
|
+
});
|
|
56
|
+
await flush(3000).then(null, () => {
|
|
57
|
+
// If telemetry flushing fails we generally don't care
|
|
58
|
+
});
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
|