Skip to content

Commit

Permalink
feat(nextjs): Adjust Next.js wizard for usage with v8 SDK (#567)
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst authored May 14, 2024
1 parent 80e13f4 commit e607f81
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 69 deletions.
13 changes: 10 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- feat(nextjs): Adjust Next.js wizard for usage with v8 SDK (#567)

## 3.22.1

- fix(wizard): Handle missing auth token in wizard API endpoint response (#566)
Expand All @@ -10,11 +14,14 @@

## 3.21.0

- feat(nextjs): Add comment to add spotlight in Sentry.init for Next.js server config (#545)
- feat(nextjs): Add comment to add spotlight in Sentry.init for Next.js server
config (#545)
- feat(nextjs): Pin installed Next.js SDK version to version 7 (#550)
- feat(remix): Add example page (#542)
- feat(sveltekit): Add comment for spotlight in Sentry.init for SvelteKit server hooks config (#546)
- ref(nextjs): Add note about `tunnelRoute` and Next.js middleware incompatibility (#544)
- feat(sveltekit): Add comment for spotlight in Sentry.init for SvelteKit server
hooks config (#546)
- ref(nextjs): Add note about `tunnelRoute` and Next.js middleware
incompatibility (#544)
- ref(remix): Remove Vite dev-mode modification step (#543)

## 3.20.5
Expand Down
109 changes: 77 additions & 32 deletions src/nextjs/nextjs-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,35 @@ import * as Sentry from '@sentry/node';
import {
abort,
abortIfCancelled,
addSentryCliConfig,
addDotEnvSentryBuildPluginFile,
askShouldCreateExamplePage,
confirmContinueIfNoOrDirtyGitRepo,
createNewConfigFile,
ensurePackageIsInstalled,
getOrAskForProjectData,
getPackageDotJson,
installPackage,
isUsingTypeScript,
printWelcome,
showCopyPasteInstructions,
} from '../utils/clack-utils';
import { SentryProjectData, WizardOptions } from '../utils/types';
import {
getFullUnderscoreErrorCopyPasteSnippet,
getGlobalErrorCopyPasteSnippet,
getInstrumentationHookContent,
getInstrumentationHookCopyPasteSnippet,
getNextjsConfigCjsAppendix,
getNextjsConfigCjsTemplate,
getNextjsConfigEsmCopyPasteSnippet,
getNextjsSentryBuildOptionsTemplate,
getNextjsWebpackPluginOptionsTemplate,
getSentryConfigContents,
getSentryDefaultGlobalErrorPage,
getSentryDefaultUnderscoreErrorPage,
getSentryExampleApiRoute,
getSentryExampleAppDirApiRoute,
getSentryExamplePageContents,
getSimpleUnderscoreErrorCopyPasteSnippet,
getWithSentryConfigOptionsTemplate,
} from './templates';
import { traceStep, withTelemetry } from '../telemetry';
import { getPackageVersion, hasPackageInstalled } from '../utils/package-json';
Expand Down Expand Up @@ -82,7 +85,7 @@ export async function runNextjsWizardWithTelemetry(
Sentry.setTag('sdk-already-installed', sdkAlreadyInstalled);

await installPackage({
packageName: '@sentry/nextjs@^7.105.0',
packageName: '@sentry/nextjs@^8',
alreadyInstalled: !!packageJson?.dependencies?.['@sentry/nextjs'],
});

Expand Down Expand Up @@ -248,7 +251,7 @@ export async function runNextjsWizardWithTelemetry(
clack.log.info(
`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(
path.join(...appDirLocation, globalErrorPageFile),
)}:`,
)}:\n`,
);

// eslint-disable-next-line no-console
Expand Down Expand Up @@ -282,7 +285,7 @@ export async function runNextjsWizardWithTelemetry(
);
}

await addSentryCliConfig({ authToken });
await addDotEnvSentryBuildPluginFile(authToken);

const mightBeUsingVercel = fs.existsSync(
path.join(process.cwd(), 'vercel.json'),
Expand Down Expand Up @@ -390,23 +393,75 @@ async function createOrMergeNextJsFiles(
});
}

const sentryWebpackOptionsTemplate = getNextjsWebpackPluginOptionsTemplate(
selectedProject.organization.slug,
selectedProject.slug,
selfHosted,
sentryUrl,
);
await traceStep('setup-instrumentation-hook', async () => {
const srcInstrumentationTsExists = fs.existsSync(
path.join(process.cwd(), 'src', 'instrumentation.ts'),
);
const srcInstrumentationJsExists = fs.existsSync(
path.join(process.cwd(), 'src', 'instrumentation.js'),
);
const instrumentationTsExists = fs.existsSync(
path.join(process.cwd(), 'instrumentation.ts'),
);
const instrumentationJsExists = fs.existsSync(
path.join(process.cwd(), 'instrumentation.js'),
);

let instrumentationHookLocation: 'src' | 'root' | 'does-not-exist';
if (srcInstrumentationTsExists || srcInstrumentationJsExists) {
instrumentationHookLocation = 'src';
} else if (instrumentationTsExists || instrumentationJsExists) {
instrumentationHookLocation = 'root';
} else {
instrumentationHookLocation = 'does-not-exist';
}

const { tunnelRoute } = sdkConfigOptions;
if (instrumentationHookLocation === 'does-not-exist') {
const srcFolderExists = fs.existsSync(path.join(process.cwd(), 'src'));

const sentryBuildOptionsTemplate = getNextjsSentryBuildOptionsTemplate({
tunnelRoute,
});
const instrumentationHookPath = srcFolderExists
? path.join(process.cwd(), 'src', 'instrumentation.ts')
: path.join(process.cwd(), 'instrumentation.ts');

const nextConfigJs = 'next.config.js';
const nextConfigMjs = 'next.config.mjs';
const successfullyCreated = await createNewConfigFile(
instrumentationHookPath,
getInstrumentationHookContent(srcFolderExists ? 'src' : 'root'),
);

if (!successfullyCreated) {
await showCopyPasteInstructions(
'instrumentation.ts',
getInstrumentationHookCopyPasteSnippet(
srcFolderExists ? 'src' : 'root',
),
);
}
} else {
await showCopyPasteInstructions(
srcInstrumentationTsExists
? 'instrumentation.ts'
: srcInstrumentationJsExists
? 'instrumentation.js'
: instrumentationTsExists
? 'instrumentation.ts'
: 'instrumentation.js',
getInstrumentationHookCopyPasteSnippet(instrumentationHookLocation),
);
}
});

await traceStep('setup-next-config', async () => {
const withSentryConfigOptionsTemplate = getWithSentryConfigOptionsTemplate({
orgSlug: selectedProject.organization.slug,
projectSlug: selectedProject.slug,
selfHosted,
url: sentryUrl,
tunnelRoute: sdkConfigOptions.tunnelRoute,
});

const nextConfigJs = 'next.config.js';
const nextConfigMjs = 'next.config.mjs';

const nextConfigJsExists = fs.existsSync(
path.join(process.cwd(), nextConfigJs),
);
Expand All @@ -419,10 +474,7 @@ async function createOrMergeNextJsFiles(

await fs.promises.writeFile(
path.join(process.cwd(), nextConfigJs),
getNextjsConfigCjsTemplate(
sentryWebpackOptionsTemplate,
sentryBuildOptionsTemplate,
),
getNextjsConfigCjsTemplate(withSentryConfigOptionsTemplate),
{ encoding: 'utf8', flag: 'w' },
);

Expand Down Expand Up @@ -460,10 +512,7 @@ async function createOrMergeNextJsFiles(
if (shouldInject) {
await fs.promises.appendFile(
path.join(process.cwd(), nextConfigJs),
getNextjsConfigCjsAppendix(
sentryWebpackOptionsTemplate,
sentryBuildOptionsTemplate,
),
getNextjsConfigCjsAppendix(withSentryConfigOptionsTemplate),
'utf8',
);

Expand Down Expand Up @@ -514,8 +563,7 @@ async function createOrMergeNextJsFiles(
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
mod.exports.default = builders.raw(`withSentryConfig(
${expressionToWrap},
${sentryWebpackOptionsTemplate},
${sentryBuildOptionsTemplate}
${withSentryConfigOptionsTemplate}
)`);
const newCode = mod.generate().code;

Expand Down Expand Up @@ -550,10 +598,7 @@ async function createOrMergeNextJsFiles(

// eslint-disable-next-line no-console
console.log(
getNextjsConfigEsmCopyPasteSnippet(
sentryWebpackOptionsTemplate,
sentryBuildOptionsTemplate,
),
getNextjsConfigEsmCopyPasteSnippet(withSentryConfigOptionsTemplate),
);

const shouldContinue = await abortIfCancelled(
Expand Down
96 changes: 65 additions & 31 deletions src/nextjs/templates.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
import chalk from 'chalk';
import { makeCodeSnippet } from '../utils/clack-utils';

export function getNextjsWebpackPluginOptionsTemplate(
orgSlug: string,
projectSlug: string,
selfHosted: boolean,
url: string,
): string {
type WithSentryConfigOptions = {
orgSlug: string;
projectSlug: string;
selfHosted: boolean;
url: string;
tunnelRoute: boolean;
};

export function getWithSentryConfigOptionsTemplate({
orgSlug,
projectSlug,
selfHosted,
tunnelRoute,
url,
}: WithSentryConfigOptions): string {
return `{
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
// Suppresses source map uploading logs during build
silent: true,
org: "${orgSlug}",
project: "${projectSlug}",${selfHosted ? `\n url: "${url}"` : ''}
}`;
}
type SentryNextjsBuildOptions = {
tunnelRoute: boolean;
};
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
export function getNextjsSentryBuildOptionsTemplate({
tunnelRoute,
}: SentryNextjsBuildOptions): string {
return `{
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
Expand All @@ -48,7 +49,7 @@ export function getNextjsSentryBuildOptionsTemplate({
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Enables automatic instrumentation of Vercel Cron Monitors.
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
Expand All @@ -57,8 +58,7 @@ export function getNextjsSentryBuildOptionsTemplate({
}

export function getNextjsConfigCjsTemplate(
sentryWebpackPluginOptionsTemplate: string,
sentryBuildOptionsTemplate: string,
withSentryConfigOptionsTemplate: string,
): string {
return `const { withSentryConfig } = require("@sentry/nextjs");
Expand All @@ -67,15 +67,13 @@ const nextConfig = {};
module.exports = withSentryConfig(
nextConfig,
${sentryWebpackPluginOptionsTemplate},
${sentryBuildOptionsTemplate}
${withSentryConfigOptionsTemplate}
);
`;
}

export function getNextjsConfigCjsAppendix(
sentryWebpackPluginOptionsTemplate: string,
sentryBuildOptionsTemplate: string,
withSentryConfigOptionsTemplate: string,
): string {
return `
Expand All @@ -85,15 +83,13 @@ const { withSentryConfig } = require("@sentry/nextjs");
module.exports = withSentryConfig(
module.exports,
${sentryWebpackPluginOptionsTemplate},
${sentryBuildOptionsTemplate}
${withSentryConfigOptionsTemplate}
);
`;
}

export function getNextjsConfigEsmCopyPasteSnippet(
sentryWebpackPluginOptionsTemplate: string,
sentryBuildOptionsTemplate: string,
withSentryConfigOptionsTemplate: string,
): string {
return `
Expand All @@ -102,8 +98,7 @@ import { withSentryConfig } from "@sentry/nextjs";
export default withSentryConfig(
yourNextConfig,
${sentryWebpackPluginOptionsTemplate},
${sentryBuildOptionsTemplate}
${withSentryConfigOptionsTemplate}
);
`;
}
Expand Down Expand Up @@ -152,7 +147,7 @@ export function getSentryConfigContents(
if (config === 'server') {
spotlightOption = `
// uncomment the line below to enable Spotlight (https://spotlightjs.com)
// Uncomment the line below to enable Spotlight (https://spotlightjs.com)
// spotlight: process.env.NODE_ENV === 'development',
`;
}
Expand Down Expand Up @@ -344,6 +339,45 @@ YourCustomErrorComponent.getInitialProps = async (contextData${
`;
}

export function getInstrumentationHookContent(
instrumentationHookLocation: 'src' | 'root',
) {
return `export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('${
instrumentationHookLocation === 'root' ? '.' : '..'
}/sentry.server.config');
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('${
instrumentationHookLocation === 'root' ? '.' : '..'
}/sentry.edge.config');
}
}
`;
}

export function getInstrumentationHookCopyPasteSnippet(
instrumentationHookLocation: 'src' | 'root',
) {
return makeCodeSnippet(true, (unchanged, plus) => {
return unchanged(`export ${plus('async')} function register() {
${plus(`if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('${
instrumentationHookLocation === 'root' ? '.' : '..'
}/sentry.server.config');
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('${
instrumentationHookLocation === 'root' ? '.' : '..'
}/sentry.edge.config');
}`)}
}`);
});
}

export function getSentryDefaultGlobalErrorPage() {
return `"use client";
Expand Down
Loading

0 comments on commit e607f81

Please sign in to comment.