diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 6f7f76ab6e23..c7a1f7225865 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -85,8 +85,10 @@ "@sentry/opentelemetry": "9.12.0", "@sentry/react": "9.12.0", "@sentry/vercel-edge": "9.12.0", - "@sentry/webpack-plugin": "3.2.4", + "@sentry/webpack-plugin": "3.3.0-alpha.1", + "@sentry/bundler-plugin-core": "3.3.0-alpha.1", "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..c670344b4caf --- /dev/null +++ b/packages/nextjs/src/config/buildPluginOptions.ts @@ -0,0 +1,138 @@ +import * as path from 'path'; +import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; +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..c2e60b2513af --- /dev/null +++ b/packages/nextjs/src/config/runAfterProductionCompile.ts @@ -0,0 +1,61 @@ +import type { SentryBuildOptions } from './types'; +import { getWebpackBuildFunctionCalled } from './util'; +import { getBuildPluginOptions } from './buildPluginOptions'; +import { glob } from 'glob'; +import { loadModule } from '@sentry/core'; +import type { createSentryBuildPluginManager as createSentryBuildPluginManagerType } from '@sentry/bundler-plugin-core'; + +/** + * 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 af7e40f5b58c..51bc98b7ac32 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -21,8 +21,8 @@ import type { WebpackConfigObjectWithModuleRules, WebpackEntryProperty, } from './types'; -import { getWebpackPluginOptions } from './webpackPluginOptions'; -import { getNextjsVersion } from './util'; +import { getBuildPluginOptions } from './buildPluginOptions'; +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. @@ -52,6 +52,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 @@ -393,8 +395,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 530659b2a703..000000000000 --- a/packages/nextjs/src/config/webpackPluginOptions.ts +++ /dev/null @@ -1,121 +0,0 @@ -import * as path from 'path'; -import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; -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 0ecb2caee9b9..39171ba85bad 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -13,6 +13,7 @@ import type { } from './types'; import { constructWebpackConfigFunction } from './webpack'; import { getNextjsVersion } from './util'; +import { handleAfterProductionCompile } from './runAfterProductionCompile'; import * as fs from 'fs'; import * as path from 'path'; @@ -206,6 +207,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 d2aad8fb19a0..419c00635a46 100644 --- a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts +++ b/packages/nextjs/test/config/webpack/buildPluginOptions.test.ts @@ -1,33 +1,10 @@ import { describe, it, expect } 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' }, @@ -60,6 +37,8 @@ describe('getWebpackPluginOptions()', () => { }, }, 'my-release', + 'webpack-client', + '/my/project/dir/.next', ); expect(generatedPluginOptions.authToken).toBe('my-auth-token'); @@ -119,9 +98,7 @@ describe('getWebpackPluginOptions()', () => { }); it('forwards bundleSizeOptimization options', () => { - const buildContext = generateBuildContext({ isServer: false }); - const generatedPluginOptions = getWebpackPluginOptions( - buildContext, + const generatedPluginOptions = getBuildPluginOptions( { bundleSizeOptimizations: { excludeTracing: true, @@ -129,6 +106,8 @@ describe('getWebpackPluginOptions()', () => { }, }, undefined, + 'webpack-client', + '/my/project/dir/.next', ); expect(generatedPluginOptions).toMatchObject({ @@ -140,8 +119,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: [], @@ -149,8 +127,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: [ @@ -164,8 +141,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: [ @@ -179,20 +160,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: [ @@ -206,8 +191,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 aaedb3d729df..2d8c4cc92ce1 100644 --- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts +++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest'; // mock helper functions not tested directly in this file import '../mocks'; -import * as getWebpackPluginOptionsModule from '../../../src/config/webpackPluginOptions'; +import * as getBuildPluginOptionsModule from '../../../src/config/buildPluginOptions'; import { CLIENT_SDK_CONFIG_FILE, clientBuildContext, @@ -55,7 +55,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', @@ -71,19 +71,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 346dae607107..c97a869ce7fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6464,6 +6464,11 @@ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.2.4.tgz#c0877df6e5ce227bf51754bf27da2fa5227af847" integrity sha512-yBzRn3GEUSv1RPtE4xB4LnuH74ZxtdoRJ5cmQ9i6mzlmGDxlrnKuvem5++AolZTE9oJqAD3Tx2rd1PqmpWnLoA== +"@sentry/babel-plugin-component-annotate@3.3.0-alpha.1": + version "3.3.0-alpha.1" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.3.0-alpha.1.tgz#4d839cfb7f313d93be88ccfce0a54b7c66fc1080" + integrity sha512-QtiIig59zHL1cVRoNaBikjF92UBpXxWq8tBe+OupP+W8U63IbLVv2C8pUPzz3QngtzzjnTY+lDFGi6LmQrmFJw== + "@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" @@ -6492,6 +6497,20 @@ magic-string "0.30.8" unplugin "1.0.1" +"@sentry/bundler-plugin-core@3.3.0-alpha.1": + version "3.3.0-alpha.1" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.3.0-alpha.1.tgz#356d343ec9c399416cf0c193621bc1871135279c" + integrity sha512-dIbwiiuTq9RJDbltl3jrPZRVyQ/BePuEhNiP2cZ0oiQPeM9SRiZcGRZ7nmG+DBlTPp+IM51R1hhskndrVg9R1Q== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "3.3.0-alpha.1" + "@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" @@ -6639,6 +6658,15 @@ unplugin "1.0.1" uuid "^9.0.0" +"@sentry/webpack-plugin@3.3.0-alpha.1": + version "3.3.0-alpha.1" + resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-3.3.0-alpha.1.tgz#6151b01ae51dfd0a7e724ea05b48c7e0090f70b0" + integrity sha512-173XxkdjL5La9KtdKxem1z2b+p6vK/ADbXy/yRIJc0q4Ayu9AVKnIQZ1GAQ4lGFxuEHgzc6eVSY+eDE6omxUxQ== + dependencies: + "@sentry/bundler-plugin-core" "3.3.0-alpha.1" + 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"