Skip to content

Commit 0b0b904

Browse files
authored
ref(nextjs): Split withSentryConfig (#18777)
The function became bloated and unreadable. This PR just splits the function and refactors parts into utils. Closes #18778 (added automatically)
1 parent 4f2313c commit 0b0b904

File tree

9 files changed

+990
-680
lines changed

9 files changed

+990
-680
lines changed

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 0 additions & 680 deletions
This file was deleted.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import * as childProcess from 'child_process';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
import type { NextConfigObject, SentryBuildOptions } from '../types';
5+
6+
/**
7+
* Adds Sentry-related build-time variables to `nextConfig.env`.
8+
*
9+
* Note: this mutates `userNextConfig`.
10+
*
11+
* @param userNextConfig - The user's Next.js config object
12+
* @param userSentryOptions - The Sentry build options passed to `withSentryConfig`
13+
* @param releaseName - The resolved release name, if any
14+
*/
15+
export function setUpBuildTimeVariables(
16+
userNextConfig: NextConfigObject,
17+
userSentryOptions: SentryBuildOptions,
18+
releaseName: string | undefined,
19+
): void {
20+
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';
21+
const basePath = userNextConfig.basePath ?? '';
22+
23+
const rewritesTunnelPath =
24+
userSentryOptions.tunnelRoute !== undefined &&
25+
userNextConfig.output !== 'export' &&
26+
typeof userSentryOptions.tunnelRoute === 'string'
27+
? `${basePath}${userSentryOptions.tunnelRoute}`
28+
: undefined;
29+
30+
const buildTimeVariables: Record<string, string> = {
31+
// Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape
32+
// characters)
33+
_sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
34+
// Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if
35+
// `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.)
36+
_sentryRewriteFramesAssetPrefixPath: assetPrefix
37+
? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '')
38+
: '',
39+
};
40+
41+
if (userNextConfig.assetPrefix) {
42+
buildTimeVariables._assetsPrefix = userNextConfig.assetPrefix;
43+
}
44+
45+
if (userSentryOptions._experimental?.thirdPartyOriginStackFrames) {
46+
buildTimeVariables._experimentalThirdPartyOriginStackFrames = 'true';
47+
}
48+
49+
if (rewritesTunnelPath) {
50+
buildTimeVariables._sentryRewritesTunnelPath = rewritesTunnelPath;
51+
}
52+
53+
if (basePath) {
54+
buildTimeVariables._sentryBasePath = basePath;
55+
}
56+
57+
if (userNextConfig.assetPrefix) {
58+
buildTimeVariables._sentryAssetPrefix = userNextConfig.assetPrefix;
59+
}
60+
61+
if (userSentryOptions._experimental?.thirdPartyOriginStackFrames) {
62+
buildTimeVariables._experimentalThirdPartyOriginStackFrames = 'true';
63+
}
64+
65+
if (releaseName) {
66+
buildTimeVariables._sentryRelease = releaseName;
67+
}
68+
69+
if (typeof userNextConfig.env === 'object') {
70+
userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env };
71+
} else if (userNextConfig.env === undefined) {
72+
userNextConfig.env = buildTimeVariables;
73+
}
74+
}
75+
76+
/**
77+
* Returns the current git SHA (HEAD), if available.
78+
*
79+
* This is a best-effort helper and returns `undefined` if git isn't available or the cwd isn't a git repo.
80+
*/
81+
export function getGitRevision(): string | undefined {
82+
let gitRevision: string | undefined;
83+
try {
84+
gitRevision = childProcess
85+
.execSync('git rev-parse HEAD', { stdio: ['ignore', 'pipe', 'ignore'] })
86+
.toString()
87+
.trim();
88+
} catch {
89+
// noop
90+
}
91+
return gitRevision;
92+
}
93+
94+
/**
95+
* Reads the project's `instrumentation-client.(js|ts)` file contents, if present.
96+
*
97+
* @returns The file contents, or `undefined` if the file can't be found/read
98+
*/
99+
export function getInstrumentationClientFileContents(): string | void {
100+
const potentialInstrumentationClientFileLocations = [
101+
['src', 'instrumentation-client.ts'],
102+
['src', 'instrumentation-client.js'],
103+
['instrumentation-client.ts'],
104+
['instrumentation-client.js'],
105+
];
106+
107+
for (const pathSegments of potentialInstrumentationClientFileLocations) {
108+
try {
109+
return fs.readFileSync(path.join(process.cwd(), ...pathSegments), 'utf-8');
110+
} catch {
111+
// noop
112+
}
113+
}
114+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Packages we auto-instrument need to be external for instrumentation to work
2+
// Next.js externalizes some packages by default, see: https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages
3+
// Others we need to add ourselves
4+
//
5+
// NOTE: 'ai' (Vercel AI SDK) is intentionally NOT included in this list.
6+
// When externalized, Next.js doesn't properly handle the package's conditional exports,
7+
// specifically the "react-server" export condition. This causes client-side code to be
8+
// loaded in server components instead of the appropriate server-side functions.
9+
export const DEFAULT_SERVER_EXTERNAL_PACKAGES = [
10+
'amqplib',
11+
'connect',
12+
'dataloader',
13+
'express',
14+
'generic-pool',
15+
'graphql',
16+
'@hapi/hapi',
17+
'ioredis',
18+
'kafkajs',
19+
'koa',
20+
'lru-memoizer',
21+
'mongodb',
22+
'mongoose',
23+
'mysql',
24+
'mysql2',
25+
'knex',
26+
'pg',
27+
'pg-pool',
28+
'@node-redis/client',
29+
'@redis/client',
30+
'redis',
31+
'tedious',
32+
];
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { SentryBuildOptions } from '../types';
2+
import { detectActiveBundler } from '../util';
3+
4+
/**
5+
* Migrates deprecated top-level webpack options to the new `webpack.*` path for backward compatibility.
6+
* The new path takes precedence over deprecated options. This mutates the userSentryOptions object.
7+
*/
8+
export function migrateDeprecatedWebpackOptions(userSentryOptions: SentryBuildOptions): void {
9+
// Initialize webpack options if not present
10+
userSentryOptions.webpack = userSentryOptions.webpack || {};
11+
12+
const webpack = userSentryOptions.webpack;
13+
14+
const withDeprecatedFallback = <T>(
15+
newValue: T | undefined,
16+
deprecatedValue: T | undefined,
17+
message: string,
18+
): T | undefined => {
19+
if (deprecatedValue !== undefined) {
20+
// eslint-disable-next-line no-console
21+
console.warn(message);
22+
}
23+
24+
return newValue ?? deprecatedValue;
25+
};
26+
27+
const deprecatedMessage = (deprecatedPath: string, newPath: string): string => {
28+
const message = `[@sentry/nextjs] DEPRECATION WARNING: ${deprecatedPath} is deprecated and will be removed in a future version. Use ${newPath} instead.`;
29+
30+
// In Turbopack builds, webpack configuration is not applied, so webpack-scoped options won't have any effect.
31+
if (detectActiveBundler() === 'turbopack' && newPath.startsWith('webpack.')) {
32+
return `${message} (Not supported with Turbopack.)`;
33+
}
34+
35+
return message;
36+
};
37+
38+
/* eslint-disable deprecation/deprecation */
39+
// Migrate each deprecated option to the new path, but only if the new path isn't already set
40+
webpack.autoInstrumentServerFunctions = withDeprecatedFallback(
41+
webpack.autoInstrumentServerFunctions,
42+
userSentryOptions.autoInstrumentServerFunctions,
43+
deprecatedMessage('autoInstrumentServerFunctions', 'webpack.autoInstrumentServerFunctions'),
44+
);
45+
46+
webpack.autoInstrumentMiddleware = withDeprecatedFallback(
47+
webpack.autoInstrumentMiddleware,
48+
userSentryOptions.autoInstrumentMiddleware,
49+
deprecatedMessage('autoInstrumentMiddleware', 'webpack.autoInstrumentMiddleware'),
50+
);
51+
52+
webpack.autoInstrumentAppDirectory = withDeprecatedFallback(
53+
webpack.autoInstrumentAppDirectory,
54+
userSentryOptions.autoInstrumentAppDirectory,
55+
deprecatedMessage('autoInstrumentAppDirectory', 'webpack.autoInstrumentAppDirectory'),
56+
);
57+
58+
webpack.excludeServerRoutes = withDeprecatedFallback(
59+
webpack.excludeServerRoutes,
60+
userSentryOptions.excludeServerRoutes,
61+
deprecatedMessage('excludeServerRoutes', 'webpack.excludeServerRoutes'),
62+
);
63+
64+
webpack.unstable_sentryWebpackPluginOptions = withDeprecatedFallback(
65+
webpack.unstable_sentryWebpackPluginOptions,
66+
userSentryOptions.unstable_sentryWebpackPluginOptions,
67+
deprecatedMessage('unstable_sentryWebpackPluginOptions', 'webpack.unstable_sentryWebpackPluginOptions'),
68+
);
69+
70+
webpack.disableSentryConfig = withDeprecatedFallback(
71+
webpack.disableSentryConfig,
72+
userSentryOptions.disableSentryWebpackConfig,
73+
deprecatedMessage('disableSentryWebpackConfig', 'webpack.disableSentryConfig'),
74+
);
75+
76+
// Handle treeshake.removeDebugLogging specially since it's nested
77+
if (userSentryOptions.disableLogger !== undefined) {
78+
webpack.treeshake = webpack.treeshake || {};
79+
webpack.treeshake.removeDebugLogging = withDeprecatedFallback(
80+
webpack.treeshake.removeDebugLogging,
81+
userSentryOptions.disableLogger,
82+
deprecatedMessage('disableLogger', 'webpack.treeshake.removeDebugLogging'),
83+
);
84+
}
85+
86+
webpack.automaticVercelMonitors = withDeprecatedFallback(
87+
webpack.automaticVercelMonitors,
88+
userSentryOptions.automaticVercelMonitors,
89+
deprecatedMessage('automaticVercelMonitors', 'webpack.automaticVercelMonitors'),
90+
);
91+
92+
webpack.reactComponentAnnotation = withDeprecatedFallback(
93+
webpack.reactComponentAnnotation,
94+
userSentryOptions.reactComponentAnnotation,
95+
deprecatedMessage('reactComponentAnnotation', 'webpack.reactComponentAnnotation'),
96+
);
97+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import type { NextConfigObject, SentryBuildOptions } from '../types';
2+
import { getNextjsVersion } from '../util';
3+
import { setUpBuildTimeVariables } from './buildTime';
4+
import { migrateDeprecatedWebpackOptions } from './deprecatedWebpackOptions';
5+
import {
6+
getBundlerInfo,
7+
getServerExternalPackagesPatch,
8+
getTurbopackPatch,
9+
getWebpackPatch,
10+
maybeConstructTurbopackConfig,
11+
maybeEnableTurbopackSourcemaps,
12+
maybeSetUpRunAfterProductionCompileHook,
13+
maybeWarnAboutUnsupportedRunAfterProductionCompileHook,
14+
maybeWarnAboutUnsupportedTurbopack,
15+
resolveUseRunAfterProductionCompileHookOption,
16+
} from './getFinalConfigObjectBundlerUtils';
17+
import {
18+
getNextMajor,
19+
maybeCreateRouteManifest,
20+
maybeSetClientTraceMetadataOption,
21+
maybeSetInstrumentationHookOption,
22+
maybeSetUpTunnelRouteRewriteRules,
23+
resolveReleaseName,
24+
shouldReturnEarlyInExperimentalBuildMode,
25+
warnIfMissingOnRouterTransitionStartHook,
26+
} from './getFinalConfigObjectUtils';
27+
28+
/**
29+
* Materializes the final Next.js config object with Sentry's build-time integrations applied.
30+
*
31+
* Note: this mutates both `incomingUserNextConfigObject` and `userSentryOptions` (to apply defaults/migrations).
32+
*/
33+
export function getFinalConfigObject(
34+
incomingUserNextConfigObject: NextConfigObject,
35+
userSentryOptions: SentryBuildOptions,
36+
): NextConfigObject {
37+
migrateDeprecatedWebpackOptions(userSentryOptions);
38+
const releaseName = resolveReleaseName(userSentryOptions);
39+
40+
maybeSetUpTunnelRouteRewriteRules(incomingUserNextConfigObject, userSentryOptions);
41+
42+
if (shouldReturnEarlyInExperimentalBuildMode()) {
43+
return incomingUserNextConfigObject;
44+
}
45+
46+
const routeManifest = maybeCreateRouteManifest(incomingUserNextConfigObject, userSentryOptions);
47+
setUpBuildTimeVariables(incomingUserNextConfigObject, userSentryOptions, releaseName);
48+
49+
const nextJsVersion = getNextjsVersion();
50+
const nextMajor = getNextMajor(nextJsVersion);
51+
52+
maybeSetClientTraceMetadataOption(incomingUserNextConfigObject, nextJsVersion);
53+
maybeSetInstrumentationHookOption(incomingUserNextConfigObject, nextJsVersion);
54+
warnIfMissingOnRouterTransitionStartHook(userSentryOptions);
55+
56+
const bundlerInfo = getBundlerInfo(nextJsVersion);
57+
maybeWarnAboutUnsupportedTurbopack(nextJsVersion, bundlerInfo);
58+
maybeWarnAboutUnsupportedRunAfterProductionCompileHook(nextJsVersion, userSentryOptions, bundlerInfo);
59+
60+
const turboPackConfig = maybeConstructTurbopackConfig(
61+
incomingUserNextConfigObject,
62+
userSentryOptions,
63+
routeManifest,
64+
nextJsVersion,
65+
bundlerInfo,
66+
);
67+
68+
const shouldUseRunAfterProductionCompileHook = resolveUseRunAfterProductionCompileHookOption(
69+
userSentryOptions,
70+
bundlerInfo,
71+
);
72+
73+
maybeSetUpRunAfterProductionCompileHook({
74+
incomingUserNextConfigObject,
75+
userSentryOptions,
76+
releaseName,
77+
nextJsVersion,
78+
bundlerInfo,
79+
turboPackConfig,
80+
shouldUseRunAfterProductionCompileHook,
81+
});
82+
83+
maybeEnableTurbopackSourcemaps(incomingUserNextConfigObject, userSentryOptions, bundlerInfo);
84+
85+
return {
86+
...incomingUserNextConfigObject,
87+
...getServerExternalPackagesPatch(incomingUserNextConfigObject, nextMajor),
88+
...getWebpackPatch({
89+
incomingUserNextConfigObject,
90+
userSentryOptions,
91+
releaseName,
92+
routeManifest,
93+
nextJsVersion,
94+
shouldUseRunAfterProductionCompileHook,
95+
bundlerInfo,
96+
}),
97+
...getTurbopackPatch(bundlerInfo, turboPackConfig),
98+
};
99+
}

0 commit comments

Comments
 (0)