From 106fa119687efe35d51f32d816aad59ad316d3c1 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 28 Oct 2024 10:14:46 +0000 Subject: [PATCH 1/7] ref(nextjs): Make build-time value injection turbopack compatible --- packages/nextjs/src/client/index.ts | 7 +--- packages/nextjs/src/client/tunnelRoute.ts | 8 ++--- .../devErrorSymbolicationEventProcessor.ts | 7 +--- packages/nextjs/src/config/types.ts | 3 +- packages/nextjs/src/config/webpack.ts | 8 +++-- .../nextjs/src/config/webpackPluginOptions.ts | 6 ++-- .../nextjs/src/config/withSentryConfig.ts | 34 +++++++++++++++++++ packages/nextjs/src/edge/index.ts | 4 +-- .../src/edge/rewriteFramesIntegration.ts | 11 ++---- packages/nextjs/src/server/index.ts | 10 +++--- .../src/server/rewriteFramesIntegration.ts | 4 +-- packages/nextjs/test/serverSdk.test.ts | 2 +- .../nextjs/test/utils/tunnelRoute.test.ts | 14 ++++---- 13 files changed, 68 insertions(+), 50 deletions(-) diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index c50bbce37305..cb3ab698d296 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -2,7 +2,6 @@ import { addEventProcessor, applySdkMetadata } from '@sentry/core'; import type { BrowserOptions } from '@sentry/react'; import { getDefaultIntegrations as getReactDefaultIntegrations, init as reactInit } from '@sentry/react'; import type { Client, EventProcessor, Integration } from '@sentry/types'; -import { GLOBAL_OBJ } from '@sentry/utils'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; import { getVercelEnv } from '../common/getVercelEnv'; @@ -15,10 +14,6 @@ export * from '@sentry/react'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; -const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesAssetPrefixPath__: string; -}; - // Treeshakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean; @@ -64,7 +59,7 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const assetPrefixPath = globalWithInjectedValues.__rewriteFramesAssetPrefixPath__ || ''; + const assetPrefixPath = process.env.__sentryRewriteFramesAssetPrefixPath || ''; customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath })); return customDefaultIntegrations; diff --git a/packages/nextjs/src/client/tunnelRoute.ts b/packages/nextjs/src/client/tunnelRoute.ts index 3c93b93e41f2..7a67c33ff209 100644 --- a/packages/nextjs/src/client/tunnelRoute.ts +++ b/packages/nextjs/src/client/tunnelRoute.ts @@ -1,17 +1,13 @@ import type { BrowserOptions } from '@sentry/react'; -import { GLOBAL_OBJ, dsnFromString, logger } from '@sentry/utils'; +import { dsnFromString, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../common/debug-build'; -const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryRewritesTunnelPath__?: string; -}; - /** * Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option. */ export function applyTunnelRouteOption(options: BrowserOptions): void { - const tunnelRouteOption = globalWithInjectedValues.__sentryRewritesTunnelPath__; + const tunnelRouteOption = process.env.__sentryRewritesTunnelPath; if (tunnelRouteOption && options.dsn) { const dsnComponents = dsnFromString(options.dsn); if (!dsnComponents) { diff --git a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts index 6c37859a851d..48f8ac6e60c8 100644 --- a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts +++ b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts @@ -1,6 +1,5 @@ import { suppressTracing } from '@sentry/core'; import type { Event, EventHint } from '@sentry/types'; -import { GLOBAL_OBJ } from '@sentry/utils'; import type { StackFrame } from 'stacktrace-parser'; import * as stackTraceParser from 'stacktrace-parser'; @@ -10,10 +9,6 @@ type OriginalStackFrameResponse = { sourcePackage?: string; }; -const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryBasePath?: string; -}; - async function resolveStackFrame( frame: StackFrame, error: Error, @@ -32,7 +27,7 @@ async function resolveStackFrame( params.append(key, (frame[key as keyof typeof frame] ?? '').toString()); }); - let basePath = globalWithInjectedValues.__sentryBasePath ?? ''; + let basePath = process.env.__sentryBasePath ?? ''; // Prefix the basepath with a slash if it doesn't have one if (basePath !== '' && !basePath.match(/^\//)) { diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 63f678cdbc66..9ab90ae78a22 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -49,6 +49,7 @@ export type NextConfigObject = { clientTraceMetadata?: string[]; }; productionBrowserSourceMaps?: boolean; + env?: Record; }; export type SentryBuildOptions = { @@ -550,7 +551,7 @@ export type ModuleRuleUseProperty = { * Global with values we add when we inject code into people's pages, for use at runtime. */ export type EnhancedGlobal = typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; + __sentryRewriteFramesDistDir?: string; SENTRY_RELEASE?: { id: string }; SENTRY_RELEASES?: { [key: string]: { id: string } }; }; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 9656a74c4efe..0d9ff0c45c0f 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -561,6 +561,8 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi /** * Adds loaders to inject values on the global object based on user configuration. */ +// TODO(v9): Remove this loader and replace it with a nextConfig.env (https://web.archive.org/web/20240917153554/https://nextjs.org/docs/app/api-reference/next-config-js/env) or define based (https://github.com/vercel/next.js/discussions/71476) approach. +// In order to remove this loader though we need to make sure the minimum supported Next.js version includes this PR (https://github.com/vercel/next.js/pull/61194), otherwise the nextConfig.env based approach will not work, as our SDK code is not processed by Next.js. function addValueInjectionLoader( newConfig: WebpackConfigObjectWithModuleRules, userNextConfig: NextConfigObject, @@ -571,7 +573,7 @@ function addValueInjectionLoader( const isomorphicValues = { // `rewritesTunnel` set by the user in Next.js config - __sentryRewritesTunnelPath__: + __sentryRewritesTunnelPath: userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` : undefined, @@ -588,14 +590,14 @@ function addValueInjectionLoader( ...isomorphicValues, // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape // characters) - __rewriteFramesDistDir__: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', + __sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', }; const clientValues = { ...isomorphicValues, // Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if // `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.) - __rewriteFramesAssetPrefixPath__: assetPrefix + __sentryRewriteFramesAssetPrefixPath: assetPrefix ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') : '', }; diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts index 7b183047896a..b9584996329a 100644 --- a/packages/nextjs/src/config/webpackPluginOptions.ts +++ b/packages/nextjs/src/config/webpackPluginOptions.ts @@ -11,7 +11,7 @@ export function getWebpackPluginOptions( buildContext: BuildContext, sentryBuildOptions: SentryBuildOptions, ): SentryWebpackPluginOptions { - const { buildId, isServer, config: userNextConfig, dir, nextRuntime } = buildContext; + const { isServer, config: userNextConfig, dir, nextRuntime } = buildContext; const prefixInsert = !isServer ? 'Client' : nextRuntime === 'edge' ? 'Edge' : 'Node.js'; @@ -93,8 +93,8 @@ export function getWebpackPluginOptions( ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.sourcemaps, }, release: { - inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead. - name: sentryBuildOptions.release?.name ?? getSentryRelease(buildId), + inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject with nextConfig.env instead + name: sentryBuildOptions.release?.name ?? getSentryRelease('TODO'), create: sentryBuildOptions.release?.create, finalize: sentryBuildOptions.release?.finalize, dist: sentryBuildOptions.release?.dist, diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 4f5205fecfcb..9464d1c55f77 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -2,6 +2,7 @@ import { isThenable, parseSemver } from '@sentry/utils'; import * as fs from 'fs'; +import { getSentryRelease } from '@sentry/node'; import { sync as resolveSync } from 'resolve'; import type { ExportedNextConfig as NextConfig, @@ -73,6 +74,8 @@ function getFinalConfigObject( } } + setUpBuildTimeVariables(incomingUserNextConfigObject); + const nextJsVersion = getNextjsVersion(); // Add the `clientTraceMetadata` experimental option based on Next.js version. The option got introduced in Next.js version 15.0.0 (actually 14.3.0-canary.64). @@ -253,6 +256,37 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s }; } +function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void { + const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; + + const buildTimeVariables = { + // `rewritesTunnel` set by the user in Next.js config + __sentryRewritesTunnelPath: + userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' + ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` + : undefined, + SENTRY_RELEASE: + process.env.NODE_ENV === 'production' + ? undefined + : { id: userSentryOptions.release?.name ?? getSentryRelease('TODO') }, + __sentryBasePath: userNextConfig.basePath, + // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape + // characters) + __sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', + // Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if + // `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.) + __sentryRewriteFramesAssetPrefixPath: assetPrefix + ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') + : '', + }; + + if (typeof userNextConfig.env === 'object') { + userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env }; + } else if (userNextConfig.env === undefined) { + userNextConfig.env = buildTimeVariables; + } +} + function getNextjsVersion(): string | undefined { const nextjsPackageJsonPath = resolveNextjsPackageJson(); if (nextjsPackageJsonPath) { diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index fff4236bf3be..57d7ae2740ae 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -21,7 +21,7 @@ export { captureUnderscoreErrorException } from '../common/pages-router-instrume export type EdgeOptions = VercelEdgeOptions; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; + __sentryRewriteFramesDistDir?: string; }; /** Inits the Sentry NextJS SDK on the Edge Runtime. */ @@ -36,7 +36,7 @@ export function init(options: VercelEdgeOptions = {}): void { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = globalWithInjectedValues.__sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); diff --git a/packages/nextjs/src/edge/rewriteFramesIntegration.ts b/packages/nextjs/src/edge/rewriteFramesIntegration.ts index 84d5a41b0922..50d66b30bba9 100644 --- a/packages/nextjs/src/edge/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/edge/rewriteFramesIntegration.ts @@ -1,10 +1,6 @@ import { defineIntegration, rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/core'; import type { IntegrationFn, StackFrame } from '@sentry/types'; -import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils'; - -const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; -}; +import { escapeStringForRegex } from '@sentry/utils'; type StackFrameIteratee = (frame: StackFrame) => StackFrame; interface RewriteFramesOptions { @@ -14,9 +10,8 @@ interface RewriteFramesOptions { } export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { - // This value is injected at build time, based on the output directory specified in the build config. Though a default - // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + // This value is injected at build time, based on the output directory specified in the build config. + const distDirName = process.env.__sentryRewriteFramesDistDir; if (distDirName) { const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 7aade8cbd5c3..c07ed55b1497 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -42,8 +42,8 @@ export * from '@sentry/node'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; - __sentryRewritesTunnelPath__?: string; + __sentryRewriteFramesDistDir?: string; + __sentryRewritesTunnelPath?: string; }; /** @@ -109,7 +109,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = globalWithInjectedValues.__sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); } @@ -212,8 +212,8 @@ export function init(options: NodeOptions): NodeClient | undefined { // Filter out transactions for requests to the tunnel route if ( - globalWithInjectedValues.__sentryRewritesTunnelPath__ && - event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath__}` + globalWithInjectedValues.__sentryRewritesTunnelPath && + event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath}` ) { return null; } diff --git a/packages/nextjs/src/server/rewriteFramesIntegration.ts b/packages/nextjs/src/server/rewriteFramesIntegration.ts index 6438ccb0d922..121c85efbdb4 100644 --- a/packages/nextjs/src/server/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/server/rewriteFramesIntegration.ts @@ -4,7 +4,7 @@ import type { IntegrationFn, StackFrame } from '@sentry/types'; import { escapeStringForRegex } from '@sentry/utils'; const globalWithInjectedValues = global as typeof global & { - __rewriteFramesDistDir__?: string; + __sentryRewriteFramesDistDir?: string; }; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -17,7 +17,7 @@ interface RewriteFramesOptions { export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = globalWithInjectedValues.__sentryRewriteFramesDistDir; if (distDirName) { // nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index 27230874d457..43a9120dfb06 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -6,7 +6,7 @@ import { GLOBAL_OBJ } from '@sentry/utils'; import { init } from '../src/server'; // normally this is set as part of the build process, so mock it here -(GLOBAL_OBJ as typeof GLOBAL_OBJ & { __rewriteFramesDistDir__: string }).__rewriteFramesDistDir__ = '.next'; +(GLOBAL_OBJ as typeof GLOBAL_OBJ & { __sentryRewriteFramesDistDir: string }).__sentryRewriteFramesDistDir = '.next'; const nodeInit = jest.spyOn(SentryNode, 'init'); diff --git a/packages/nextjs/test/utils/tunnelRoute.test.ts b/packages/nextjs/test/utils/tunnelRoute.test.ts index 576898c061b2..c38368e64907 100644 --- a/packages/nextjs/test/utils/tunnelRoute.test.ts +++ b/packages/nextjs/test/utils/tunnelRoute.test.ts @@ -3,16 +3,16 @@ import type { BrowserOptions } from '@sentry/react'; import { applyTunnelRouteOption } from '../../src/client/tunnelRoute'; const globalWithInjectedValues = global as typeof global & { - __sentryRewritesTunnelPath__?: string; + __sentryRewritesTunnelPath?: string; }; beforeEach(() => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = undefined; + globalWithInjectedValues.__sentryRewritesTunnelPath = undefined; }); describe('applyTunnelRouteOption()', () => { it('Correctly applies `tunnelRoute` option when conditions are met', () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.sentry.io/3333333', } as BrowserOptions; @@ -23,7 +23,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't apply `tunnelRoute` when DSN is missing", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { // no dsn } as BrowserOptions; @@ -34,7 +34,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't apply `tunnelRoute` when DSN is invalid", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'invalidDsn', } as BrowserOptions; @@ -55,7 +55,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't `tunnelRoute` option when DSN is not a SaaS DSN", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@example.com/3333333', } as BrowserOptions; @@ -66,7 +66,7 @@ describe('applyTunnelRouteOption()', () => { }); it('Correctly applies `tunnelRoute` option to region DSNs', () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.us.sentry.io/3333333', } as BrowserOptions; From 218edb58327e923d8d8c6d970d310d60a1167c01 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 28 Oct 2024 13:49:13 +0000 Subject: [PATCH 2/7] Use process.env and fallback for all built-time values --- packages/nextjs/src/client/index.ts | 10 +++++++++- packages/nextjs/src/client/tunnelRoute.ts | 9 +++++++-- .../src/common/devErrorSymbolicationEventProcessor.ts | 7 ++++++- packages/nextjs/src/config/withSentryConfig.ts | 2 +- packages/nextjs/src/edge/index.ts | 2 +- packages/nextjs/src/edge/rewriteFramesIntegration.ts | 8 ++++++-- packages/nextjs/src/server/index.ts | 8 +++++--- packages/nextjs/src/server/rewriteFramesIntegration.ts | 2 +- 8 files changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index cb3ab698d296..19394165754a 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -2,6 +2,7 @@ import { addEventProcessor, applySdkMetadata } from '@sentry/core'; import type { BrowserOptions } from '@sentry/react'; import { getDefaultIntegrations as getReactDefaultIntegrations, init as reactInit } from '@sentry/react'; import type { Client, EventProcessor, Integration } from '@sentry/types'; +import { GLOBAL_OBJ } from '@sentry/utils'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; import { getVercelEnv } from '../common/getVercelEnv'; @@ -14,6 +15,10 @@ export * from '@sentry/react'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; +const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { + __sentryRewriteFramesAssetPrefixPath: string; +}; + // Treeshakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean; @@ -59,7 +64,10 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const assetPrefixPath = process.env.__sentryRewriteFramesAssetPrefixPath || ''; + const assetPrefixPath = + process.env.__sentryRewriteFramesAssetPrefixPath || + globalWithInjectedValues.__sentryRewriteFramesAssetPrefixPath || + ''; customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath })); return customDefaultIntegrations; diff --git a/packages/nextjs/src/client/tunnelRoute.ts b/packages/nextjs/src/client/tunnelRoute.ts index 7a67c33ff209..d7b396a17d72 100644 --- a/packages/nextjs/src/client/tunnelRoute.ts +++ b/packages/nextjs/src/client/tunnelRoute.ts @@ -1,13 +1,18 @@ import type { BrowserOptions } from '@sentry/react'; -import { dsnFromString, logger } from '@sentry/utils'; +import { GLOBAL_OBJ, dsnFromString, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../common/debug-build'; +const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { + __sentryRewritesTunnelPath?: string; +}; + /** * Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option. */ export function applyTunnelRouteOption(options: BrowserOptions): void { - const tunnelRouteOption = process.env.__sentryRewritesTunnelPath; + const tunnelRouteOption = + process.env.__sentryRewritesTunnelPath || globalWithInjectedValues.__sentryRewritesTunnelPath; if (tunnelRouteOption && options.dsn) { const dsnComponents = dsnFromString(options.dsn); if (!dsnComponents) { diff --git a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts index 48f8ac6e60c8..23d384f9436c 100644 --- a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts +++ b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts @@ -1,5 +1,6 @@ import { suppressTracing } from '@sentry/core'; import type { Event, EventHint } from '@sentry/types'; +import { GLOBAL_OBJ } from '@sentry/utils'; import type { StackFrame } from 'stacktrace-parser'; import * as stackTraceParser from 'stacktrace-parser'; @@ -9,6 +10,10 @@ type OriginalStackFrameResponse = { sourcePackage?: string; }; +const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { + __sentryBasePath?: string; +}; + async function resolveStackFrame( frame: StackFrame, error: Error, @@ -27,7 +32,7 @@ async function resolveStackFrame( params.append(key, (frame[key as keyof typeof frame] ?? '').toString()); }); - let basePath = process.env.__sentryBasePath ?? ''; + let basePath = process.env.__sentryBasePath ?? globalWithInjectedValues.__sentryBasePath ?? ''; // Prefix the basepath with a slash if it doesn't have one if (basePath !== '' && !basePath.match(/^\//)) { diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 9464d1c55f77..32a93e8f66e1 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -74,7 +74,7 @@ function getFinalConfigObject( } } - setUpBuildTimeVariables(incomingUserNextConfigObject); + setUpBuildTimeVariables(incomingUserNextConfigObject, userSentryOptions); const nextJsVersion = getNextjsVersion(); diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 57d7ae2740ae..c6e04385d70f 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -36,7 +36,7 @@ export function init(options: VercelEdgeOptions = {}): void { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__sentryRewriteFramesDistDir; + const distDirName = process.env.__sentryRewriteFramesDistDir || globalWithInjectedValues.__sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); diff --git a/packages/nextjs/src/edge/rewriteFramesIntegration.ts b/packages/nextjs/src/edge/rewriteFramesIntegration.ts index 50d66b30bba9..317123a3e066 100644 --- a/packages/nextjs/src/edge/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/edge/rewriteFramesIntegration.ts @@ -1,6 +1,10 @@ import { defineIntegration, rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/core'; import type { IntegrationFn, StackFrame } from '@sentry/types'; -import { escapeStringForRegex } from '@sentry/utils'; +import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils'; + +const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { + __sentryRewriteFramesDistDir?: string; +}; type StackFrameIteratee = (frame: StackFrame) => StackFrame; interface RewriteFramesOptions { @@ -11,7 +15,7 @@ interface RewriteFramesOptions { export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { // This value is injected at build time, based on the output directory specified in the build config. - const distDirName = process.env.__sentryRewriteFramesDistDir; + const distDirName = process.env.__sentryRewriteFramesDistDir || globalWithInjectedValues.__sentryRewriteFramesDistDir; if (distDirName) { const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index c07ed55b1497..8d02b1217789 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -109,7 +109,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__sentryRewriteFramesDistDir; + const distDirName = process.env.__sentryRewriteFramesDistDir || globalWithInjectedValues.__sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); } @@ -212,8 +212,10 @@ export function init(options: NodeOptions): NodeClient | undefined { // Filter out transactions for requests to the tunnel route if ( - globalWithInjectedValues.__sentryRewritesTunnelPath && - event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath}` + (globalWithInjectedValues.__sentryRewritesTunnelPath && + event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath}`) || + (process.env.__sentryRewritesTunnelPath && + event.transaction === `POST ${process.env.__sentryRewritesTunnelPath}`) ) { return null; } diff --git a/packages/nextjs/src/server/rewriteFramesIntegration.ts b/packages/nextjs/src/server/rewriteFramesIntegration.ts index 121c85efbdb4..024b11559d33 100644 --- a/packages/nextjs/src/server/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/server/rewriteFramesIntegration.ts @@ -17,7 +17,7 @@ interface RewriteFramesOptions { export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__sentryRewriteFramesDistDir; + const distDirName = process.env.__sentryRewriteFramesDistDir || globalWithInjectedValues.__sentryRewriteFramesDistDir; if (distDirName) { // nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so From 61e864d3e0e1e7f9184f903de066c58d2b82d8dd Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 29 Oct 2024 12:32:46 +0000 Subject: [PATCH 3/7] hmmm. --- packages/nextjs/src/config/git-revision.ts | 15 ++++++++ .../nextjs/src/config/webpackPluginOptions.ts | 3 +- .../nextjs/src/config/withSentryConfig.ts | 35 +++++++++++++------ 3 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 packages/nextjs/src/config/git-revision.ts diff --git a/packages/nextjs/src/config/git-revision.ts b/packages/nextjs/src/config/git-revision.ts new file mode 100644 index 000000000000..5c745c1167ac --- /dev/null +++ b/packages/nextjs/src/config/git-revision.ts @@ -0,0 +1,15 @@ +import * as childProcess from 'child_process'; + +/** Grabs the current git sha */ +export function getGitRevision(): string | undefined { + let gitRevision: string | undefined; + try { + gitRevision = childProcess + .execSync('git rev-parse HEAD', { stdio: ['ignore', 'ignore', 'ignore'] }) + .toString() + .trim(); + } catch (e) { + // noop + } + return gitRevision; +} diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts index b9584996329a..de76bfb52a00 100644 --- a/packages/nextjs/src/config/webpackPluginOptions.ts +++ b/packages/nextjs/src/config/webpackPluginOptions.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { getSentryRelease } from '@sentry/node'; import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; +import { getGitRevision } from './git-revision'; import type { BuildContext, NextConfigObject, SentryBuildOptions } from './types'; /** @@ -94,7 +95,7 @@ export function getWebpackPluginOptions( }, release: { inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject with nextConfig.env instead - name: sentryBuildOptions.release?.name ?? getSentryRelease('TODO'), + name: sentryBuildOptions.release?.name ?? getSentryRelease(getGitRevision()), create: sentryBuildOptions.release?.create, finalize: sentryBuildOptions.release?.finalize, dist: sentryBuildOptions.release?.dist, diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 32a93e8f66e1..c30eeb4b8b0c 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -4,6 +4,7 @@ import { isThenable, parseSemver } from '@sentry/utils'; import * as fs from 'fs'; import { getSentryRelease } from '@sentry/node'; import { sync as resolveSync } from 'resolve'; +import { getGitRevision } from './git-revision'; import type { ExportedNextConfig as NextConfig, NextConfigFunction, @@ -258,18 +259,17 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void { const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; + const release = + process.env.NODE_ENV === 'production' + ? userSentryOptions.release?.name ?? getSentryRelease(getGitRevision()) + : undefined; + const rewritesTunnelPath = + userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' + ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` + : undefined; + const basePath = userNextConfig.basePath; - const buildTimeVariables = { - // `rewritesTunnel` set by the user in Next.js config - __sentryRewritesTunnelPath: - userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' - ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` - : undefined, - SENTRY_RELEASE: - process.env.NODE_ENV === 'production' - ? undefined - : { id: userSentryOptions.release?.name ?? getSentryRelease('TODO') }, - __sentryBasePath: userNextConfig.basePath, + const buildTimeVariables: Record = { // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape // characters) __sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', @@ -280,6 +280,19 @@ function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOpt : '', }; + if (release) { + // SENTRY_RELEASE will turn into `process.env.SENTRY_RELEASE` which the SDK will pick up. + buildTimeVariables.SENTRY_RELEASE = release; + } + + if (rewritesTunnelPath) { + buildTimeVariables.__sentryRewritesTunnelPath = rewritesTunnelPath; + } + + if (basePath) { + buildTimeVariables.__sentryBasePath = basePath; + } + if (typeof userNextConfig.env === 'object') { userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env }; } else if (userNextConfig.env === undefined) { From 3f7863da6a08446cd088502e54bc618cfd8bc9fb Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 4 Nov 2024 15:23:06 +0000 Subject: [PATCH 4/7] Fix stuff --- packages/nextjs/src/client/index.ts | 6 ++--- packages/nextjs/src/client/tunnelRoute.ts | 5 ++-- .../devErrorSymbolicationEventProcessor.ts | 4 ++-- packages/nextjs/src/config/git-revision.ts | 15 ------------ packages/nextjs/src/config/types.ts | 3 ++- packages/nextjs/src/config/webpack.ts | 8 +++---- .../nextjs/src/config/webpackPluginOptions.ts | 7 +++--- .../nextjs/src/config/withSentryConfig.ts | 23 +++++++------------ packages/nextjs/src/edge/index.ts | 4 ++-- .../src/edge/rewriteFramesIntegration.ts | 4 ++-- packages/nextjs/src/server/index.ts | 14 +++++------ .../src/server/rewriteFramesIntegration.ts | 4 ++-- packages/nextjs/test/serverSdk.test.ts | 2 +- .../nextjs/test/utils/tunnelRoute.test.ts | 14 +++++------ 14 files changed, 45 insertions(+), 68 deletions(-) delete mode 100644 packages/nextjs/src/config/git-revision.ts diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 19394165754a..a1e6aba11308 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -16,7 +16,7 @@ export * from '@sentry/react'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryRewriteFramesAssetPrefixPath: string; + _sentryRewriteFramesAssetPrefixPath: string; }; // Treeshakable guard to remove all code related to tracing @@ -65,8 +65,8 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. const assetPrefixPath = - process.env.__sentryRewriteFramesAssetPrefixPath || - globalWithInjectedValues.__sentryRewriteFramesAssetPrefixPath || + process.env._sentryRewriteFramesAssetPrefixPath || + globalWithInjectedValues._sentryRewriteFramesAssetPrefixPath || ''; customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath })); diff --git a/packages/nextjs/src/client/tunnelRoute.ts b/packages/nextjs/src/client/tunnelRoute.ts index d7b396a17d72..59ce4cfe82b7 100644 --- a/packages/nextjs/src/client/tunnelRoute.ts +++ b/packages/nextjs/src/client/tunnelRoute.ts @@ -4,15 +4,14 @@ import { GLOBAL_OBJ, dsnFromString, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../common/debug-build'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryRewritesTunnelPath?: string; + _sentryRewritesTunnelPath?: string; }; /** * Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option. */ export function applyTunnelRouteOption(options: BrowserOptions): void { - const tunnelRouteOption = - process.env.__sentryRewritesTunnelPath || globalWithInjectedValues.__sentryRewritesTunnelPath; + const tunnelRouteOption = process.env._sentryRewritesTunnelPath || globalWithInjectedValues._sentryRewritesTunnelPath; if (tunnelRouteOption && options.dsn) { const dsnComponents = dsnFromString(options.dsn); if (!dsnComponents) { diff --git a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts index 23d384f9436c..143bf6fef6ef 100644 --- a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts +++ b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts @@ -11,7 +11,7 @@ type OriginalStackFrameResponse = { }; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryBasePath?: string; + _sentryBasePath?: string; }; async function resolveStackFrame( @@ -32,7 +32,7 @@ async function resolveStackFrame( params.append(key, (frame[key as keyof typeof frame] ?? '').toString()); }); - let basePath = process.env.__sentryBasePath ?? globalWithInjectedValues.__sentryBasePath ?? ''; + let basePath = process.env._sentryBasePath ?? globalWithInjectedValues._sentryBasePath ?? ''; // Prefix the basepath with a slash if it doesn't have one if (basePath !== '' && !basePath.match(/^\//)) { diff --git a/packages/nextjs/src/config/git-revision.ts b/packages/nextjs/src/config/git-revision.ts deleted file mode 100644 index 5c745c1167ac..000000000000 --- a/packages/nextjs/src/config/git-revision.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as childProcess from 'child_process'; - -/** Grabs the current git sha */ -export function getGitRevision(): string | undefined { - let gitRevision: string | undefined; - try { - gitRevision = childProcess - .execSync('git rev-parse HEAD', { stdio: ['ignore', 'ignore', 'ignore'] }) - .toString() - .trim(); - } catch (e) { - // noop - } - return gitRevision; -} diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 9ab90ae78a22..e0d2f27315a5 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -49,6 +49,7 @@ export type NextConfigObject = { clientTraceMetadata?: string[]; }; productionBrowserSourceMaps?: boolean; + // https://nextjs.org/docs/pages/api-reference/next-config-js/env env?: Record; }; @@ -551,7 +552,7 @@ export type ModuleRuleUseProperty = { * Global with values we add when we inject code into people's pages, for use at runtime. */ export type EnhancedGlobal = typeof GLOBAL_OBJ & { - __sentryRewriteFramesDistDir?: string; + _sentryRewriteFramesDistDir?: string; SENTRY_RELEASE?: { id: string }; SENTRY_RELEASES?: { [key: string]: { id: string } }; }; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 0d9ff0c45c0f..c0288a569747 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -573,7 +573,7 @@ function addValueInjectionLoader( const isomorphicValues = { // `rewritesTunnel` set by the user in Next.js config - __sentryRewritesTunnelPath: + _sentryRewritesTunnelPath: userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` : undefined, @@ -583,21 +583,21 @@ function addValueInjectionLoader( SENTRY_RELEASE: buildContext.dev ? undefined : { id: userSentryOptions.release?.name ?? getSentryRelease(buildContext.buildId) }, - __sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined, + _sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined, }; const serverValues = { ...isomorphicValues, // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape // characters) - __sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', + _sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', }; const clientValues = { ...isomorphicValues, // Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if // `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.) - __sentryRewriteFramesAssetPrefixPath: assetPrefix + _sentryRewriteFramesAssetPrefixPath: assetPrefix ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') : '', }; diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts index de76bfb52a00..7b183047896a 100644 --- a/packages/nextjs/src/config/webpackPluginOptions.ts +++ b/packages/nextjs/src/config/webpackPluginOptions.ts @@ -1,7 +1,6 @@ import * as path from 'path'; import { getSentryRelease } from '@sentry/node'; import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; -import { getGitRevision } from './git-revision'; import type { BuildContext, NextConfigObject, SentryBuildOptions } from './types'; /** @@ -12,7 +11,7 @@ export function getWebpackPluginOptions( buildContext: BuildContext, sentryBuildOptions: SentryBuildOptions, ): SentryWebpackPluginOptions { - const { isServer, config: userNextConfig, dir, nextRuntime } = buildContext; + const { buildId, isServer, config: userNextConfig, dir, nextRuntime } = buildContext; const prefixInsert = !isServer ? 'Client' : nextRuntime === 'edge' ? 'Edge' : 'Node.js'; @@ -94,8 +93,8 @@ export function getWebpackPluginOptions( ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.sourcemaps, }, release: { - inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject with nextConfig.env instead - name: sentryBuildOptions.release?.name ?? getSentryRelease(getGitRevision()), + inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead. + name: sentryBuildOptions.release?.name ?? getSentryRelease(buildId), create: sentryBuildOptions.release?.create, finalize: sentryBuildOptions.release?.finalize, dist: sentryBuildOptions.release?.dist, diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index c30eeb4b8b0c..0e109858d805 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -2,9 +2,7 @@ import { isThenable, parseSemver } from '@sentry/utils'; import * as fs from 'fs'; -import { getSentryRelease } from '@sentry/node'; import { sync as resolveSync } from 'resolve'; -import { getGitRevision } from './git-revision'; import type { ExportedNextConfig as NextConfig, NextConfigFunction, @@ -22,6 +20,7 @@ let showedExportModeTunnelWarning = false; * @param sentryBuildOptions Additional options to configure instrumentation and * @returns The modified config to be exported */ +// TODO(v9): Always return an async function here to allow us to do async things like grabbing a deterministic build ID. export function withSentryConfig(nextConfig?: C, sentryBuildOptions: SentryBuildOptions = {}): C { const castNextConfig = (nextConfig as NextConfig) || {}; if (typeof castNextConfig === 'function') { @@ -257,12 +256,11 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s }; } +// TODO(v9): Inject the release into all the bundles. This is breaking because grabbing the build ID if the user provides +// it in `generateBuildId` (https://nextjs.org/docs/app/api-reference/next-config-js/generateBuildId) is async but we do +// not turn the next config function in the type it was passed. function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void { const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; - const release = - process.env.NODE_ENV === 'production' - ? userSentryOptions.release?.name ?? getSentryRelease(getGitRevision()) - : undefined; const rewritesTunnelPath = userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` @@ -272,25 +270,20 @@ function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOpt const buildTimeVariables: Record = { // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape // characters) - __sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', + _sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', // Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if // `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.) - __sentryRewriteFramesAssetPrefixPath: assetPrefix + _sentryRewriteFramesAssetPrefixPath: assetPrefix ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') : '', }; - if (release) { - // SENTRY_RELEASE will turn into `process.env.SENTRY_RELEASE` which the SDK will pick up. - buildTimeVariables.SENTRY_RELEASE = release; - } - if (rewritesTunnelPath) { - buildTimeVariables.__sentryRewritesTunnelPath = rewritesTunnelPath; + buildTimeVariables._sentryRewritesTunnelPath = rewritesTunnelPath; } if (basePath) { - buildTimeVariables.__sentryBasePath = basePath; + buildTimeVariables._sentryBasePath = basePath; } if (typeof userNextConfig.env === 'object') { diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index c6e04385d70f..5bfc8cca054b 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -21,7 +21,7 @@ export { captureUnderscoreErrorException } from '../common/pages-router-instrume export type EdgeOptions = VercelEdgeOptions; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryRewriteFramesDistDir?: string; + _sentryRewriteFramesDistDir?: string; }; /** Inits the Sentry NextJS SDK on the Edge Runtime. */ @@ -36,7 +36,7 @@ export function init(options: VercelEdgeOptions = {}): void { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = process.env.__sentryRewriteFramesDistDir || globalWithInjectedValues.__sentryRewriteFramesDistDir; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); diff --git a/packages/nextjs/src/edge/rewriteFramesIntegration.ts b/packages/nextjs/src/edge/rewriteFramesIntegration.ts index 317123a3e066..15a541311ed1 100644 --- a/packages/nextjs/src/edge/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/edge/rewriteFramesIntegration.ts @@ -3,7 +3,7 @@ import type { IntegrationFn, StackFrame } from '@sentry/types'; import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryRewriteFramesDistDir?: string; + _sentryRewriteFramesDistDir?: string; }; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -15,7 +15,7 @@ interface RewriteFramesOptions { export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { // This value is injected at build time, based on the output directory specified in the build config. - const distDirName = process.env.__sentryRewriteFramesDistDir || globalWithInjectedValues.__sentryRewriteFramesDistDir; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 8d02b1217789..bdff0de922b2 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -42,8 +42,8 @@ export * from '@sentry/node'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryRewriteFramesDistDir?: string; - __sentryRewritesTunnelPath?: string; + _sentryRewriteFramesDistDir?: string; + _sentryRewritesTunnelPath?: string; }; /** @@ -109,7 +109,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = process.env.__sentryRewriteFramesDistDir || globalWithInjectedValues.__sentryRewriteFramesDistDir; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); } @@ -212,10 +212,10 @@ export function init(options: NodeOptions): NodeClient | undefined { // Filter out transactions for requests to the tunnel route if ( - (globalWithInjectedValues.__sentryRewritesTunnelPath && - event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath}`) || - (process.env.__sentryRewritesTunnelPath && - event.transaction === `POST ${process.env.__sentryRewritesTunnelPath}`) + (globalWithInjectedValues._sentryRewritesTunnelPath && + event.transaction === `POST ${globalWithInjectedValues._sentryRewritesTunnelPath}`) || + (process.env._sentryRewritesTunnelPath && + event.transaction === `POST ${process.env._sentryRewritesTunnelPath}`) ) { return null; } diff --git a/packages/nextjs/src/server/rewriteFramesIntegration.ts b/packages/nextjs/src/server/rewriteFramesIntegration.ts index 024b11559d33..33bc7d90cb99 100644 --- a/packages/nextjs/src/server/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/server/rewriteFramesIntegration.ts @@ -4,7 +4,7 @@ import type { IntegrationFn, StackFrame } from '@sentry/types'; import { escapeStringForRegex } from '@sentry/utils'; const globalWithInjectedValues = global as typeof global & { - __sentryRewriteFramesDistDir?: string; + _sentryRewriteFramesDistDir?: string; }; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -17,7 +17,7 @@ interface RewriteFramesOptions { export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = process.env.__sentryRewriteFramesDistDir || globalWithInjectedValues.__sentryRewriteFramesDistDir; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { // nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index 43a9120dfb06..78a0115f1577 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -6,7 +6,7 @@ import { GLOBAL_OBJ } from '@sentry/utils'; import { init } from '../src/server'; // normally this is set as part of the build process, so mock it here -(GLOBAL_OBJ as typeof GLOBAL_OBJ & { __sentryRewriteFramesDistDir: string }).__sentryRewriteFramesDistDir = '.next'; +(GLOBAL_OBJ as typeof GLOBAL_OBJ & { _sentryRewriteFramesDistDir: string })._sentryRewriteFramesDistDir = '.next'; const nodeInit = jest.spyOn(SentryNode, 'init'); diff --git a/packages/nextjs/test/utils/tunnelRoute.test.ts b/packages/nextjs/test/utils/tunnelRoute.test.ts index c38368e64907..05aa992f39e6 100644 --- a/packages/nextjs/test/utils/tunnelRoute.test.ts +++ b/packages/nextjs/test/utils/tunnelRoute.test.ts @@ -3,16 +3,16 @@ import type { BrowserOptions } from '@sentry/react'; import { applyTunnelRouteOption } from '../../src/client/tunnelRoute'; const globalWithInjectedValues = global as typeof global & { - __sentryRewritesTunnelPath?: string; + _sentryRewritesTunnelPath?: string; }; beforeEach(() => { - globalWithInjectedValues.__sentryRewritesTunnelPath = undefined; + globalWithInjectedValues._sentryRewritesTunnelPath = undefined; }); describe('applyTunnelRouteOption()', () => { it('Correctly applies `tunnelRoute` option when conditions are met', () => { - globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.sentry.io/3333333', } as BrowserOptions; @@ -23,7 +23,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't apply `tunnelRoute` when DSN is missing", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { // no dsn } as BrowserOptions; @@ -34,7 +34,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't apply `tunnelRoute` when DSN is invalid", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'invalidDsn', } as BrowserOptions; @@ -55,7 +55,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't `tunnelRoute` option when DSN is not a SaaS DSN", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@example.com/3333333', } as BrowserOptions; @@ -66,7 +66,7 @@ describe('applyTunnelRouteOption()', () => { }); it('Correctly applies `tunnelRoute` option to region DSNs', () => { - globalWithInjectedValues.__sentryRewritesTunnelPath = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.us.sentry.io/3333333', } as BrowserOptions; From 68a560c78e20f4c569635bc88d887b68b6760943 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 6 Nov 2024 09:44:57 +0000 Subject: [PATCH 5/7] Review suggestion --- packages/nextjs/src/config/withSentryConfig.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 0e109858d805..539e75c20596 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -261,11 +261,11 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s // not turn the next config function in the type it was passed. function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void { const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; + const basePath = userNextConfig.basePath ?? ''; const rewritesTunnelPath = userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' - ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` + ? `${basePath}${userSentryOptions.tunnelRoute}` : undefined; - const basePath = userNextConfig.basePath; const buildTimeVariables: Record = { // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape From 906a551cb1b2a3cd027e4d83de04633e988799ab Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 6 Nov 2024 10:46:58 +0000 Subject: [PATCH 6/7] lint --- packages/browser-utils/src/metrics/browserMetrics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 278ce9c9784e..09714e90c11f 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, startInactiveSpan } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan } from '@sentry/core'; import { setMeasurement } from '@sentry/core'; import type { Measurements, Span, SpanAttributes, StartSpanOptions } from '@sentry/types'; import { browserPerformanceTimeOrigin, getComponentName, htmlTreeAsString, logger, parseUrl } from '@sentry/utils'; From 1b1af556383ca7ceb42f93ec31ef681610430d8c Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 6 Nov 2024 11:06:59 +0000 Subject: [PATCH 7/7] size limit --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 8b506b8f683b..4903d38fef62 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -79,7 +79,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, - limit: '78.1 KB', + limit: '78.2 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback)',