Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nextjs): Add feature selection #631

Merged
merged 11 commits into from
Aug 7, 2024
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- feat(nextjs): Add feature selection (#631)

## 3.26.0

- fix(nextjs): Don't add '.env.sentry-build-plugin' to .gitignore if it's already there (#610)
Expand Down
27 changes: 23 additions & 4 deletions src/nextjs/nextjs-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
confirmContinueIfNoOrDirtyGitRepo,
createNewConfigFile,
ensurePackageIsInstalled,
featureSelectionPrompt,
getOrAskForProjectData,
getPackageDotJson,
installPackage,
Expand Down Expand Up @@ -333,6 +334,23 @@ async function createOrMergeNextJsFiles(
sentryUrl: string,
sdkConfigOptions: SDKConfigOptions,
) {
const selectedFeatures = await featureSelectionPrompt([
{
id: 'performance',
prompt: `Do you want to enable ${chalk.bold(
'Tracing',
)} to track the performance of your application?`,
enabledHint: 'recommended',
},
{
id: 'replay',
prompt: `Do you want to enable ${chalk.bold(
'Sentry Session Replay',
)} to get reproduction of frontend errors via user sessions?`,
enabledHint: 'recommended, but increases bundle size',
},
] as const);

const typeScriptDetected = isUsingTypeScript();

const configVariants = ['server', 'client', 'edge'] as const;
Expand Down Expand Up @@ -390,6 +408,7 @@ async function createOrMergeNextJsFiles(
getSentryConfigContents(
selectedProject.keys[0].dsn.public,
configVariant,
selectedFeatures,
),
{ encoding: 'utf8', flag: 'w' },
);
Expand Down Expand Up @@ -854,7 +873,7 @@ async function askShouldSetTunnelRoute() {
const shouldSetTunnelRoute = await abortIfCancelled(
clack.select({
message:
'Do you want to route Sentry requests in the browser through your NextJS server to avoid ad blockers?',
'Do you want to route Sentry requests in the browser through your Next.js server to avoid ad blockers?',
options: [
{
label: 'Yes',
Expand All @@ -867,7 +886,7 @@ async function askShouldSetTunnelRoute() {
hint: 'Browser errors and events might be blocked by ad blockers before being sent to Sentry',
},
],
initialValue: false,
initialValue: true,
}),
);

Expand All @@ -891,15 +910,15 @@ async function askShouldEnableReactComponentAnnotation() {
{
label: 'Yes',
value: true,
hint: 'Annotates React component names (increases bundle size)',
hint: 'Annotates React component names - increases bundle size',
},
{
label: 'No',
value: false,
hint: 'Continue without React component annotations',
},
],
initialValue: false,
initialValue: true,
}),
);

Expand Down
55 changes: 33 additions & 22 deletions src/nextjs/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,26 @@ export default withSentryConfig(
`;
}

function getClientIntegrationsSnippet(features: { replay: boolean }) {
if (features.replay) {
return `

// Add optional integrations for additional features
integrations: [
Sentry.replayIntegration(),
],`;
}

return '';
}

export function getSentryConfigContents(
dsn: string,
config: 'server' | 'client' | 'edge',
selectedFeaturesMap: {
replay: boolean;
performance: boolean;
},
): string {
let primer;
if (config === 'server') {
Expand All @@ -132,32 +149,29 @@ export function getSentryConfigContents(
// https://docs.sentry.io/platforms/javascript/guides/nextjs/`;
}

let additionalOptions = '';
const integrationsOptions = getClientIntegrationsSnippet(selectedFeaturesMap);

let replayOptions = '';
if (config === 'client') {
additionalOptions = `
replaysOnErrorSampleRate: 1.0,
if (selectedFeaturesMap.replay) {
replayOptions += `

// Define how likely Replay events are sampled.
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0.1,

// You can remove this option if you're not planning to use the Sentry Session Replay feature:
integrations: [
Sentry.replayIntegration({
// Additional Replay configuration goes in here, for example:
maskAllText: true,
blockAllMedia: true,
}),
],`;
// Define how likely Replay events are sampled when an error occurs.
replaysOnErrorSampleRate: 1.0,`;
}
}

let spotlightOption = '';
if (config === 'server') {
spotlightOption = `
let performanceOptions = '';
if (selectedFeaturesMap.performance) {
performanceOptions += `

// Uncomment the line below to enable Spotlight (https://spotlightjs.com)
// spotlight: process.env.NODE_ENV === 'development',
`;
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,`;
}

// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
Expand All @@ -166,13 +180,10 @@ export function getSentryConfigContents(
import * as Sentry from "@sentry/nextjs";

Sentry.init({
dsn: "${dsn}",

// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
dsn: "${dsn}",${integrationsOptions}${performanceOptions}${replayOptions}

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,${additionalOptions}${spotlightOption}
debug: false,
});
`;
}
Expand Down
2 changes: 1 addition & 1 deletion src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export async function run(argv: Args) {
{ value: 'android', label: 'Android' },
{ value: 'cordova', label: 'Cordova' },
{ value: 'electron', label: 'Electron' },
{ value: 'nextjs', label: 'NextJS' },
{ value: 'nextjs', label: 'Next.js' },
{ value: 'remix', label: 'Remix' },
{ value: 'sveltekit', label: 'SvelteKit' },
{ value: 'sourcemaps', label: 'Configure Source Maps Upload' },
Expand Down
2 changes: 1 addition & 1 deletion src/sourcemaps/sourcemaps-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ async function askForUsedBundlerTool(): Promise<SupportedTools> {
{
label: 'Next.js',
value: 'nextjs',
hint: 'Select this option if you want to set up source maps in a NextJS project.',
hint: 'Select this option if you want to set up source maps in a Next.js project.',
},
{
label: 'Remix',
Expand Down
35 changes: 34 additions & 1 deletion src/utils/clack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { setInterval } from 'timers';
import { URL } from 'url';
import * as Sentry from '@sentry/node';
import { hasPackageInstalled, PackageDotJson } from './package-json';
import { SentryProjectData, WizardOptions } from './types';
import { Feature, SentryProjectData, WizardOptions } from './types';
import { traceStep } from '../telemetry';
import {
detectPackageManger,
Expand Down Expand Up @@ -1277,3 +1277,36 @@ export async function askShouldCreateExamplePage(
),
);
}

export async function featureSelectionPrompt<F extends ReadonlyArray<Feature>>(
features: F,
): Promise<{ [key in F[number]['id']]: boolean }> {
return traceStep('feature-selection', async () => {
const selectedFeatures: Record<string, boolean> = {};

for (const feature of features) {
const selected = await abortIfCancelled(
clack.select({
message: feature.prompt,
initialValue: true,
options: [
{
value: true,
label: 'Yes',
hint: feature.enabledHint,
},
{
value: false,
label: 'No',
hint: feature.disabledHint,
},
],
}),
);

selectedFeatures[feature.id] = selected;
}

return selectedFeatures as { [key in F[number]['id']]: boolean };
});
}
7 changes: 7 additions & 0 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,10 @@ export type WizardOptions = {
selfHosted: boolean;
};
};

export interface Feature {
id: string;
prompt: string;
enabledHint?: string;
disabledHint?: string;
}
Loading
Loading