diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index a095a8b0eb1f..28926bc8d114 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -85,8 +85,10 @@ "@sentry/opentelemetry": "9.15.0", "@sentry/react": "9.15.0", "@sentry/vercel-edge": "9.15.0", - "@sentry/webpack-plugin": "3.3.1", + "@sentry/webpack-plugin": "3.4.0", + "@sentry/bundler-plugin-core": "3.4.0", "chalk": "3.0.0", + "glob": "^9.3.2", "resolve": "1.22.8", "rollup": "4.35.0", "stacktrace-parser": "^0.1.10" diff --git a/packages/nextjs/src/config/buildPluginOptions.ts b/packages/nextjs/src/config/buildPluginOptions.ts new file mode 100644 index 000000000000..683127b21601 --- /dev/null +++ b/packages/nextjs/src/config/buildPluginOptions.ts @@ -0,0 +1,138 @@ +import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; +import * as path from 'path'; +import type { SentryBuildOptions } from './types'; + +/** + * Combine default and user-provided SentryWebpackPlugin options, accounting for whether we're building server files or + * client files. + */ +export function getBuildPluginOptions( + sentryBuildOptions: SentryBuildOptions, + releaseName: string | undefined, + mode: 'webpack-nodejs' | 'webpack-edge' | 'webpack-client' | 'after-production-build', + distDirAbsPath: string, +): SentryWebpackPluginOptions { + const loggerPrefixOverride = { + 'webpack-nodejs': '[@sentry/nextjs - Node.js]', + 'webpack-edge': '[@sentry/nextjs - Edge]', + 'webpack-client': '[@sentry/nextjs - Client]', + 'after-production-build': '[@sentry/nextjs]', + }[mode]; + + const sourcemapUploadAssets: string[] = []; + const sourcemapUploadIgnore: string[] = []; + const filesToDeleteAfterUpload: string[] = []; + + // We need to convert paths to posix because Glob patterns use `\` to escape + // glob characters. This clashes with Windows path separators. + // See: https://www.npmjs.com/package/glob + const normalizedDistDirAbsPath = distDirAbsPath.replace(/\\/g, '/'); + + if (mode === 'after-production-build') { + sourcemapUploadAssets.push( + path.posix.join(normalizedDistDirAbsPath, '**'), // This is normally where Next.js outputs things + ); + if (sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload) { + filesToDeleteAfterUpload.push( + path.posix.join(normalizedDistDirAbsPath, '**', '*.js.map'), + path.posix.join(normalizedDistDirAbsPath, '**', '*.mjs.map'), + path.posix.join(normalizedDistDirAbsPath, '**', '*.cjs.map'), + ); + } + } else { + if (mode === 'webpack-nodejs' || mode === 'webpack-edge') { + sourcemapUploadAssets.push( + path.posix.join(normalizedDistDirAbsPath, 'server', '**'), // This is normally where Next.js outputs things + path.posix.join(normalizedDistDirAbsPath, 'serverless', '**'), // This was the output location for serverless Next.js + ); + } else { + if (sentryBuildOptions.widenClientFileUpload) { + sourcemapUploadAssets.push(path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', '**')); + } else { + sourcemapUploadAssets.push( + path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'pages', '**'), + path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'app', '**'), + ); + } + + // TODO: We should think about uploading these when `widenClientFileUpload` is `true`. They may be useful in some situations. + sourcemapUploadIgnore.push( + path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'framework-*'), + path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'framework.*'), + path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'main-*'), + path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'polyfills-*'), + path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'webpack-*'), + ); + } + + if (sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload) { + filesToDeleteAfterUpload.push( + // We only care to delete client bundle source maps because they would be the ones being served. + // Removing the server source maps crashes Vercel builds for (thus far) unknown reasons: + // https://github.com/getsentry/sentry-javascript/issues/13099 + path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.js.map'), + path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.mjs.map'), + path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.cjs.map'), + ); + } + } + + return { + authToken: sentryBuildOptions.authToken, + headers: sentryBuildOptions.headers, + org: sentryBuildOptions.org, + project: sentryBuildOptions.project, + telemetry: sentryBuildOptions.telemetry, + debug: sentryBuildOptions.debug, + reactComponentAnnotation: { + ...sentryBuildOptions.reactComponentAnnotation, + ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.reactComponentAnnotation, + }, + silent: sentryBuildOptions.silent, + url: sentryBuildOptions.sentryUrl, + sourcemaps: { + disable: sentryBuildOptions.sourcemaps?.disable, + rewriteSources(source) { + if (source.startsWith('webpack://_N_E/')) { + return source.replace('webpack://_N_E/', ''); + } else if (source.startsWith('webpack://')) { + return source.replace('webpack://', ''); + } else { + return source; + } + }, + assets: sentryBuildOptions.sourcemaps?.assets ?? sourcemapUploadAssets, + ignore: sentryBuildOptions.sourcemaps?.ignore ?? sourcemapUploadIgnore, + filesToDeleteAfterUpload: filesToDeleteAfterUpload, + ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.sourcemaps, + }, + release: + releaseName !== undefined + ? { + inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead. + name: releaseName, + create: sentryBuildOptions.release?.create, + finalize: sentryBuildOptions.release?.finalize, + dist: sentryBuildOptions.release?.dist, + vcsRemote: sentryBuildOptions.release?.vcsRemote, + setCommits: sentryBuildOptions.release?.setCommits, + deploy: sentryBuildOptions.release?.deploy, + ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.release, + } + : { + inject: false, + create: false, + finalize: false, + }, + bundleSizeOptimizations: { + ...sentryBuildOptions.bundleSizeOptimizations, + }, + _metaOptions: { + loggerPrefixOverride, + telemetry: { + metaFramework: 'nextjs', + }, + }, + ...sentryBuildOptions.unstable_sentryWebpackPluginOptions, + }; +} diff --git a/packages/nextjs/src/config/runAfterProductionCompile.ts b/packages/nextjs/src/config/runAfterProductionCompile.ts new file mode 100644 index 000000000000..e4a403561be8 --- /dev/null +++ b/packages/nextjs/src/config/runAfterProductionCompile.ts @@ -0,0 +1,61 @@ +import type { createSentryBuildPluginManager as createSentryBuildPluginManagerType } from '@sentry/bundler-plugin-core'; +import { loadModule } from '@sentry/core'; +import { glob } from 'glob'; +import { getBuildPluginOptions } from './buildPluginOptions'; +import type { SentryBuildOptions } from './types'; +import { getWebpackBuildFunctionCalled } from './util'; + +/** + * A function to do Sentry stuff for the `runAfterProductionCompile` Next.js hook + */ +export async function handleAfterProductionCompile( + buildInfo: { distDir: string; releaseName: string | undefined }, + sentryBuildOptions: SentryBuildOptions, +): Promise { + // The handleAfterProductionCompile function is only relevant if we are using Turbopack instead of Webpack, meaning we noop if we detect that we did any webpack logic + if (getWebpackBuildFunctionCalled()) { + if (sentryBuildOptions.debug) { + // eslint-disable-next-line no-console + console.debug('[@sentry/nextjs] Not running runAfterProductionCompile logic because Webpack context was ran.'); + } + return; + } + + const { createSentryBuildPluginManager } = + loadModule<{ createSentryBuildPluginManager: typeof createSentryBuildPluginManagerType }>( + '@sentry/bundler-plugin-core', + module, + ) ?? {}; + + if (!createSentryBuildPluginManager) { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/nextjs] Could not load build manager package. Will not run runAfterProductionCompile logic.', + ); + return; + } + + const sentryBuildPluginManager = createSentryBuildPluginManager( + getBuildPluginOptions(sentryBuildOptions, buildInfo.releaseName, 'after-production-build', buildInfo.distDir), + { + buildTool: 'turbopack', + loggerPrefix: '[@sentry/nextjs]', + }, + ); + + const buildArtifactsPromise = glob( + ['/**/*.js', '/**/*.mjs', '/**/*.cjs', '/**/*.js.map', '/**/*.mjs.map', '/**/*.cjs.map'].map( + q => `${q}?(\\?*)?(#*)`, + ), // We want to allow query and hashes strings at the end of files + { + root: buildInfo.distDir, + absolute: true, + nodir: true, + }, + ); + + await sentryBuildPluginManager.telemetry.emitBundlerPluginExecutionSignal(); + await sentryBuildPluginManager.createRelease(); + await sentryBuildPluginManager.uploadSourcemaps(await buildArtifactsPromise); + await sentryBuildPluginManager.deleteArtifacts(); +} diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 965233d08b76..aea9a10fafd3 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -49,6 +49,9 @@ export type NextConfigObject = { productionBrowserSourceMaps?: boolean; // https://nextjs.org/docs/pages/api-reference/next-config-js/env env?: Record; + compiler?: { + runAfterProductionCompile?: (metadata: { projectDir: string; distDir: string }) => Promise; + }; }; export type SentryBuildOptions = { diff --git a/packages/nextjs/src/config/util.ts b/packages/nextjs/src/config/util.ts index a88e68a57135..688f9858b1fb 100644 --- a/packages/nextjs/src/config/util.ts +++ b/packages/nextjs/src/config/util.ts @@ -1,3 +1,4 @@ +import { GLOBAL_OBJ } from '@sentry/core'; import * as fs from 'fs'; import { sync as resolveSync } from 'resolve'; @@ -27,3 +28,21 @@ function resolveNextjsPackageJson(): string | undefined { return undefined; } } + +/** + * Leaves a mark on the global scope in the Next.js build context that webpack has been executed. + */ +export function setWebpackBuildFunctionCalled(): void { + // Let the rest of the execution context know that we are using Webpack to build. + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + (GLOBAL_OBJ as any)._sentryWebpackBuildFunctionCalled = true; +} + +/** + * Checks whether webpack has been executed fot the current Next.js build. + */ +export function getWebpackBuildFunctionCalled(): boolean { + // Let the rest of the execution context know that we are using Webpack to build. + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + return !!(GLOBAL_OBJ as any)._sentryWebpackBuildFunctionCalled; +} diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 322f2e320624..52be429ef18d 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -7,6 +7,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { sync as resolveSync } from 'resolve'; import type { VercelCronsConfig } from '../common/types'; +import { getBuildPluginOptions } from './buildPluginOptions'; // Note: If you need to import a type from Webpack, do it in `types.ts` and export it from there. Otherwise, our // circular dependency check thinks this file is importing from itself. See https://github.com/pahen/madge/issues/306. import type { @@ -20,8 +21,7 @@ import type { WebpackConfigObjectWithModuleRules, WebpackEntryProperty, } from './types'; -import { getNextjsVersion } from './util'; -import { getWebpackPluginOptions } from './webpackPluginOptions'; +import { getNextjsVersion, setWebpackBuildFunctionCalled } from './util'; // Next.js runs webpack 3 times, once for the client, the server, and for edge. Because we don't want to print certain // warnings 3 times, we keep track of them here. @@ -51,6 +51,8 @@ export function constructWebpackConfigFunction( incomingConfig: WebpackConfigObject, buildContext: BuildContext, ): WebpackConfigObject { + setWebpackBuildFunctionCalled(); + const { isServer, dev: isDev, dir: projectDir } = buildContext; const runtime = isServer ? (buildContext.nextRuntime === 'edge' ? 'edge' : 'server') : 'client'; // Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161 @@ -392,8 +394,19 @@ export function constructWebpackConfigFunction( } newConfig.plugins = newConfig.plugins || []; + + const mode = ({ client: 'webpack-client', server: 'webpack-nodejs', edge: 'webpack-edge' } as const)[runtime]; + + // We need to convert paths to posix because Glob patterns use `\` to escape + // glob characters. This clashes with Windows path separators. + // See: https://www.npmjs.com/package/glob + const projectDir = buildContext.dir.replace(/\\/g, '/'); + // `.next` is the default directory + const distDir = (userNextConfig as NextConfigObject).distDir?.replace(/\\/g, '/') ?? '.next'; + const distDirAbsPath = path.posix.join(projectDir, distDir); + const sentryWebpackPluginInstance = sentryWebpackPlugin( - getWebpackPluginOptions(buildContext, userSentryOptions, releaseName), + getBuildPluginOptions(userSentryOptions, releaseName, mode, distDirAbsPath), ); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access sentryWebpackPluginInstance._name = 'sentry-webpack-plugin'; // For tests and debugging. Serves no other purpose. diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts deleted file mode 100644 index 335c7bea8976..000000000000 --- a/packages/nextjs/src/config/webpackPluginOptions.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; -import * as path from 'path'; -import type { BuildContext, NextConfigObject, SentryBuildOptions } from './types'; - -/** - * Combine default and user-provided SentryWebpackPlugin options, accounting for whether we're building server files or - * client files. - */ -export function getWebpackPluginOptions( - buildContext: BuildContext, - sentryBuildOptions: SentryBuildOptions, - releaseName: string | undefined, -): SentryWebpackPluginOptions { - const { isServer, config: userNextConfig, dir, nextRuntime } = buildContext; - - const prefixInsert = !isServer ? 'Client' : nextRuntime === 'edge' ? 'Edge' : 'Node.js'; - - // We need to convert paths to posix because Glob patterns use `\` to escape - // glob characters. This clashes with Windows path separators. - // See: https://www.npmjs.com/package/glob - const projectDir = dir.replace(/\\/g, '/'); - // `.next` is the default directory - const distDir = (userNextConfig as NextConfigObject).distDir?.replace(/\\/g, '/') ?? '.next'; - const distDirAbsPath = path.posix.join(projectDir, distDir); - - const sourcemapUploadAssets: string[] = []; - const sourcemapUploadIgnore: string[] = []; - - if (isServer) { - sourcemapUploadAssets.push( - path.posix.join(distDirAbsPath, 'server', '**'), // This is normally where Next.js outputs things - path.posix.join(distDirAbsPath, 'serverless', '**'), // This was the output location for serverless Next.js - ); - } else { - if (sentryBuildOptions.widenClientFileUpload) { - sourcemapUploadAssets.push(path.posix.join(distDirAbsPath, 'static', 'chunks', '**')); - } else { - sourcemapUploadAssets.push( - path.posix.join(distDirAbsPath, 'static', 'chunks', 'pages', '**'), - path.posix.join(distDirAbsPath, 'static', 'chunks', 'app', '**'), - ); - } - - // TODO: We should think about uploading these when `widenClientFileUpload` is `true`. They may be useful in some situations. - sourcemapUploadIgnore.push( - path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework-*'), - path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework.*'), - path.posix.join(distDirAbsPath, 'static', 'chunks', 'main-*'), - path.posix.join(distDirAbsPath, 'static', 'chunks', 'polyfills-*'), - path.posix.join(distDirAbsPath, 'static', 'chunks', 'webpack-*'), - ); - } - - return { - authToken: sentryBuildOptions.authToken, - headers: sentryBuildOptions.headers, - org: sentryBuildOptions.org, - project: sentryBuildOptions.project, - telemetry: sentryBuildOptions.telemetry, - debug: sentryBuildOptions.debug, - reactComponentAnnotation: { - ...sentryBuildOptions.reactComponentAnnotation, - ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.reactComponentAnnotation, - }, - silent: sentryBuildOptions.silent, - url: sentryBuildOptions.sentryUrl, - sourcemaps: { - disable: sentryBuildOptions.sourcemaps?.disable, - rewriteSources(source) { - if (source.startsWith('webpack://_N_E/')) { - return source.replace('webpack://_N_E/', ''); - } else if (source.startsWith('webpack://')) { - return source.replace('webpack://', ''); - } else { - return source; - } - }, - assets: sentryBuildOptions.sourcemaps?.assets ?? sourcemapUploadAssets, - ignore: sentryBuildOptions.sourcemaps?.ignore ?? sourcemapUploadIgnore, - filesToDeleteAfterUpload: sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload - ? [ - // We only care to delete client bundle source maps because they would be the ones being served. - // Removing the server source maps crashes Vercel builds for (thus far) unknown reasons: - // https://github.com/getsentry/sentry-javascript/issues/13099 - path.posix.join(distDirAbsPath, 'static', '**', '*.js.map'), - path.posix.join(distDirAbsPath, 'static', '**', '*.mjs.map'), - path.posix.join(distDirAbsPath, 'static', '**', '*.cjs.map'), - ] - : undefined, - ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.sourcemaps, - }, - release: - releaseName !== undefined - ? { - inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead. - name: releaseName, - create: sentryBuildOptions.release?.create, - finalize: sentryBuildOptions.release?.finalize, - dist: sentryBuildOptions.release?.dist, - vcsRemote: sentryBuildOptions.release?.vcsRemote, - setCommits: sentryBuildOptions.release?.setCommits, - deploy: sentryBuildOptions.release?.deploy, - ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.release, - } - : { - inject: false, - create: false, - finalize: false, - }, - bundleSizeOptimizations: { - ...sentryBuildOptions.bundleSizeOptimizations, - }, - _metaOptions: { - loggerPrefixOverride: `[@sentry/nextjs - ${prefixInsert}]`, - telemetry: { - metaFramework: 'nextjs', - }, - }, - ...sentryBuildOptions.unstable_sentryWebpackPluginOptions, - }; -} diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 7eedcc763e9b..75b3ab89da09 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -5,6 +5,7 @@ import { getSentryRelease } from '@sentry/node'; import * as childProcess from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; +import { handleAfterProductionCompile } from './runAfterProductionCompile'; import type { ExportedNextConfig as NextConfig, NextConfigFunction, @@ -205,6 +206,30 @@ function getFinalConfigObject( } } + // Used for turbopack. Runs sourcemaps upload & release management via the `runAfterProductionCompile` hook. + if (incomingUserNextConfigObject?.compiler?.runAfterProductionCompile === undefined) { + incomingUserNextConfigObject.compiler ??= {}; + incomingUserNextConfigObject.compiler.runAfterProductionCompile = async ({ distDir }) => { + await handleAfterProductionCompile({ releaseName, distDir }, userSentryOptions); + }; + } else if (typeof incomingUserNextConfigObject.compiler.runAfterProductionCompile === 'function') { + incomingUserNextConfigObject.compiler.runAfterProductionCompile = new Proxy( + incomingUserNextConfigObject.compiler.runAfterProductionCompile, + { + async apply(target, thisArg, argArray) { + const { distDir }: { distDir: string } = argArray[0] ?? { distDir: '.next' }; // should never be undefined but to be defensive + await target.apply(thisArg, argArray); + await handleAfterProductionCompile({ releaseName, distDir }, userSentryOptions); + }, + }, + ); + } else { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/nextjs] The configured `compiler.runAfterProductionCompile` option is not a function. Will not run source map and release management logic.', + ); + } + return { ...incomingUserNextConfigObject, webpack: constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions, releaseName), diff --git a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts b/packages/nextjs/test/config/webpack/buildPluginOptions.test.ts similarity index 73% rename from packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts rename to packages/nextjs/test/config/webpack/buildPluginOptions.test.ts index 1dd0cfa95d5b..067ef52510e9 100644 --- a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts +++ b/packages/nextjs/test/config/webpack/buildPluginOptions.test.ts @@ -1,32 +1,9 @@ import { describe, expect, it } from 'vitest'; -import type { BuildContext, NextConfigObject } from '../../../src/config/types'; -import { getWebpackPluginOptions } from '../../../src/config/webpackPluginOptions'; +import { getBuildPluginOptions } from '../../../src/config/buildPluginOptions'; -function generateBuildContext(overrides: { - dir?: string; - isServer: boolean; - nextjsConfig?: NextConfigObject; -}): BuildContext { - return { - dev: false, // The plugin is not included in dev mode - isServer: overrides.isServer, - buildId: 'test-build-id', - dir: overrides.dir ?? '/my/project/dir', - config: overrides.nextjsConfig ?? {}, - totalPages: 2, - defaultLoaders: true, - webpack: { - version: '4.0.0', - DefinePlugin: {} as any, - }, - }; -} - -describe('getWebpackPluginOptions()', () => { +describe('getBuildPluginOptions()', () => { it('forwards relevant options', () => { - const buildContext = generateBuildContext({ isServer: false }); - const generatedPluginOptions = getWebpackPluginOptions( - buildContext, + const generatedPluginOptions = getBuildPluginOptions( { authToken: 'my-auth-token', headers: { 'my-test-header': 'test' }, @@ -59,6 +36,8 @@ describe('getWebpackPluginOptions()', () => { }, }, 'my-release', + 'webpack-client', + '/my/project/dir/.next', ); expect(generatedPluginOptions.authToken).toBe('my-auth-token'); @@ -118,9 +97,7 @@ describe('getWebpackPluginOptions()', () => { }); it('forwards bundleSizeOptimization options', () => { - const buildContext = generateBuildContext({ isServer: false }); - const generatedPluginOptions = getWebpackPluginOptions( - buildContext, + const generatedPluginOptions = getBuildPluginOptions( { bundleSizeOptimizations: { excludeTracing: true, @@ -128,6 +105,8 @@ describe('getWebpackPluginOptions()', () => { }, }, undefined, + 'webpack-client', + '/my/project/dir/.next', ); expect(generatedPluginOptions).toMatchObject({ @@ -139,8 +118,7 @@ describe('getWebpackPluginOptions()', () => { }); it('returns the right `assets` and `ignore` values during the server build', () => { - const buildContext = generateBuildContext({ isServer: true }); - const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}, undefined); + const generatedPluginOptions = getBuildPluginOptions({}, undefined, 'webpack-nodejs', '/my/project/dir/.next'); expect(generatedPluginOptions.sourcemaps).toMatchObject({ assets: ['/my/project/dir/.next/server/**', '/my/project/dir/.next/serverless/**'], ignore: [], @@ -148,8 +126,7 @@ describe('getWebpackPluginOptions()', () => { }); it('returns the right `assets` and `ignore` values during the client build', () => { - const buildContext = generateBuildContext({ isServer: false }); - const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}, undefined); + const generatedPluginOptions = getBuildPluginOptions({}, undefined, 'webpack-client', '/my/project/dir/.next'); expect(generatedPluginOptions.sourcemaps).toMatchObject({ assets: ['/my/project/dir/.next/static/chunks/pages/**', '/my/project/dir/.next/static/chunks/app/**'], ignore: [ @@ -163,8 +140,12 @@ describe('getWebpackPluginOptions()', () => { }); it('returns the right `assets` and `ignore` values during the client build with `widenClientFileUpload`', () => { - const buildContext = generateBuildContext({ isServer: false }); - const generatedPluginOptions = getWebpackPluginOptions(buildContext, { widenClientFileUpload: true }, undefined); + const generatedPluginOptions = getBuildPluginOptions( + { widenClientFileUpload: true }, + undefined, + 'webpack-client', + '/my/project/dir/.next', + ); expect(generatedPluginOptions.sourcemaps).toMatchObject({ assets: ['/my/project/dir/.next/static/chunks/**'], ignore: [ @@ -178,20 +159,24 @@ describe('getWebpackPluginOptions()', () => { }); it('sets `sourcemaps.disable` plugin options to true when `sourcemaps.disable` is true', () => { - const buildContext = generateBuildContext({ isServer: false }); - const generatedPluginOptions = getWebpackPluginOptions(buildContext, { sourcemaps: { disable: true } }, undefined); + const generatedPluginOptions = getBuildPluginOptions( + { sourcemaps: { disable: true } }, + undefined, + 'webpack-client', + '/my/project/dir/.next', + ); expect(generatedPluginOptions.sourcemaps).toMatchObject({ disable: true, }); }); it('passes posix paths to the plugin', () => { - const buildContext = generateBuildContext({ - dir: 'C:\\my\\windows\\project\\dir', - nextjsConfig: { distDir: '.dist\\v1' }, - isServer: false, - }); - const generatedPluginOptions = getWebpackPluginOptions(buildContext, { widenClientFileUpload: true }, undefined); + const generatedPluginOptions = getBuildPluginOptions( + { widenClientFileUpload: true }, + undefined, + 'webpack-client', + 'C:\\my\\windows\\project\\dir\\.dist\\v1', + ); expect(generatedPluginOptions.sourcemaps).toMatchObject({ assets: ['C:/my/windows/project/dir/.dist/v1/static/chunks/**'], ignore: [ @@ -205,8 +190,7 @@ describe('getWebpackPluginOptions()', () => { }); it('sets options to not create a release or do any release operations when releaseName is undefined', () => { - const buildContext = generateBuildContext({ isServer: false }); - const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}, undefined); + const generatedPluginOptions = getBuildPluginOptions({}, undefined, 'webpack-client', '/my/project/dir/.next'); expect(generatedPluginOptions).toMatchObject({ release: { diff --git a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts index 3a8e86b94e29..d76f03024873 100644 --- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts +++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts @@ -2,7 +2,7 @@ import '../mocks'; import * as core from '@sentry/core'; import { describe, expect, it, vi } from 'vitest'; -import * as getWebpackPluginOptionsModule from '../../../src/config/webpackPluginOptions'; +import * as getBuildPluginOptionsModule from '../../../src/config/buildPluginOptions'; import { CLIENT_SDK_CONFIG_FILE, clientBuildContext, @@ -53,7 +53,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('automatically enables deleteSourcemapsAfterUpload for client builds when not explicitly set', async () => { - const getWebpackPluginOptionsSpy = vi.spyOn(getWebpackPluginOptionsModule, 'getWebpackPluginOptions'); + const getBuildPluginOptionsSpy = vi.spyOn(getBuildPluginOptionsModule, 'getBuildPluginOptions'); vi.spyOn(core, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', @@ -69,19 +69,18 @@ describe('constructWebpackConfigFunction()', () => { }, }); - expect(getWebpackPluginOptionsSpy).toHaveBeenCalledWith( - expect.objectContaining({ - isServer: false, - }), + expect(getBuildPluginOptionsSpy).toHaveBeenCalledWith( expect.objectContaining({ sourcemaps: { deleteSourcemapsAfterUpload: true, }, }), undefined, + expect.any(String), + expect.any(String), ); - getWebpackPluginOptionsSpy.mockRestore(); + getBuildPluginOptionsSpy.mockRestore(); }); it('preserves unrelated webpack config options', async () => { diff --git a/yarn.lock b/yarn.lock index 334e255ebb3b..ddcf4171df82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6460,6 +6460,11 @@ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.3.1.tgz#baecd89396cbb4659565a4e8efe7f0a71b19262a" integrity sha512-5GOxGT7lZN+I8A7Vp0rWY+726FDKEw8HnFiebe51rQrMbfGfCu2Aw9uSM0nT9OG6xhV6WvGccIcCszTPs4fUZQ== +"@sentry/babel-plugin-component-annotate@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.4.0.tgz#f47a7652e16f84556df82cbc38f0004bca1335d1" + integrity sha512-tSzfc3aE7m0PM0Aj7HBDet5llH9AB9oc+tBQ8AvOqUSnWodLrNCuWeQszJ7mIBovD3figgCU3h0cvI6U5cDtsg== + "@sentry/bundler-plugin-core@2.22.6": version "2.22.6" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.6.tgz#a1ea1fd43700a3ece9e7db016997e79a2782b87d" @@ -6502,6 +6507,20 @@ magic-string "0.30.8" unplugin "1.0.1" +"@sentry/bundler-plugin-core@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.4.0.tgz#3a3459aba94cbeb093347f5730f15df25153fd0a" + integrity sha512-X1Q41AsQ6xcT6hB4wYmBDBukndKM/inT4IsR7pdKLi7ICpX2Qq6lisamBAEPCgEvnLpazSFguaiC0uiwMKAdqw== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "3.4.0" + "@sentry/cli" "2.42.2" + dotenv "^16.3.1" + find-up "^5.0.0" + glob "^9.3.2" + magic-string "0.30.8" + unplugin "1.0.1" + "@sentry/cli-darwin@2.42.2": version "2.42.2" resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.42.2.tgz#a32a4f226e717122b37d9969e8d4d0e14779f720" @@ -6649,6 +6668,15 @@ unplugin "1.0.1" uuid "^9.0.0" +"@sentry/webpack-plugin@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-3.4.0.tgz#5dc7f895b35eaea268bf85f23c59ba40ac65489b" + integrity sha512-i+nAxxniJV5ovijojjTF5n+Yj08Xk8my+vm8+oo0C0I7xcnI2gOKft6B0sJOq01CNbo85X5m/3/edL0PKoWE9w== + dependencies: + "@sentry/bundler-plugin-core" "3.4.0" + unplugin "1.0.1" + uuid "^9.0.0" + "@sigstore/protobuf-specs@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz#957cb64ea2f5ce527cc9cf02a096baeb0d2b99b4"