@@ -2,38 +2,264 @@ import type { Options as SentryBuildPluginOptions } from '@sentry/bundler-plugin
22import * as path from 'path' ;
33import type { SentryBuildOptions } from './types' ;
44
5+ const LOGGER_PREFIXES = {
6+ 'webpack-nodejs' : '[@sentry/nextjs - Node.js]' ,
7+ 'webpack-edge' : '[@sentry/nextjs - Edge]' ,
8+ 'webpack-client' : '[@sentry/nextjs - Client]' ,
9+ 'after-production-compile-webpack' : '[@sentry/nextjs - After Production Compile (Webpack)]' ,
10+ 'after-production-compile-turbopack' : '[@sentry/nextjs - After Production Compile (Turbopack)]' ,
11+ } as const ;
12+
13+ // File patterns for source map operations
14+ // We use both glob patterns and directory paths for the sourcemap upload and deletion
15+ // -> Direct CLI invocation handles file paths better than glob patterns
16+ // -> Webpack/Bundler needs glob patterns as this is the format that is used by the plugin
17+ const FILE_PATTERNS = {
18+ SERVER : {
19+ GLOB : 'server/**' ,
20+ PATH : 'server' ,
21+ } ,
22+ SERVERLESS : 'serverless/**' ,
23+ STATIC_CHUNKS : {
24+ GLOB : 'static/chunks/**' ,
25+ PATH : 'static/chunks' ,
26+ } ,
27+ STATIC_CHUNKS_PAGES : {
28+ GLOB : 'static/chunks/pages/**' ,
29+ PATH : 'static/chunks/pages' ,
30+ } ,
31+ STATIC_CHUNKS_APP : {
32+ GLOB : 'static/chunks/app/**' ,
33+ PATH : 'static/chunks/app' ,
34+ } ,
35+ MAIN_CHUNKS : 'static/chunks/main-*' ,
36+ FRAMEWORK_CHUNKS : 'static/chunks/framework-*' ,
37+ FRAMEWORK_CHUNKS_DOT : 'static/chunks/framework.*' ,
38+ POLYFILLS_CHUNKS : 'static/chunks/polyfills-*' ,
39+ WEBPACK_CHUNKS : 'static/chunks/webpack-*' ,
40+ } as const ;
41+
42+ // Source map file extensions to delete
43+ const SOURCEMAP_EXTENSIONS = [ '*.js.map' , '*.mjs.map' , '*.cjs.map' ] as const ;
44+
45+ type BuildTool = keyof typeof LOGGER_PREFIXES ;
46+
47+ /**
48+ * Normalizes Windows paths to POSIX format for glob patterns
49+ */
50+ export function normalizePathForGlob ( distPath : string ) : string {
51+ return distPath . replace ( / \\ / g, '/' ) ;
52+ }
53+
54+ /**
55+ * These functions are used to get the correct pattern for the sourcemap upload based on the build tool and the usage context
56+ * -> Direct CLI invocation handles file paths better than glob patterns
57+ */
58+ function getServerPattern ( { useDirectoryPath = false } : { useDirectoryPath ?: boolean } ) : string {
59+ return useDirectoryPath ? FILE_PATTERNS . SERVER . PATH : FILE_PATTERNS . SERVER . GLOB ;
60+ }
61+
62+ function getStaticChunksPattern ( { useDirectoryPath = false } : { useDirectoryPath ?: boolean } ) : string {
63+ return useDirectoryPath ? FILE_PATTERNS . STATIC_CHUNKS . PATH : FILE_PATTERNS . STATIC_CHUNKS . GLOB ;
64+ }
65+
66+ function getStaticChunksPagesPattern ( { useDirectoryPath = false } : { useDirectoryPath ?: boolean } ) : string {
67+ return useDirectoryPath ? FILE_PATTERNS . STATIC_CHUNKS_PAGES . PATH : FILE_PATTERNS . STATIC_CHUNKS_PAGES . GLOB ;
68+ }
69+
70+ function getStaticChunksAppPattern ( { useDirectoryPath = false } : { useDirectoryPath ?: boolean } ) : string {
71+ return useDirectoryPath ? FILE_PATTERNS . STATIC_CHUNKS_APP . PATH : FILE_PATTERNS . STATIC_CHUNKS_APP . GLOB ;
72+ }
73+
574/**
6- * Get Sentry Build Plugin options for the runAfterProductionCompile hook.
75+ * Creates file patterns for source map uploads based on build tool and options
76+ */
77+ function createSourcemapUploadAssetPatterns (
78+ normalizedDistPath : string ,
79+ buildTool : BuildTool ,
80+ widenClientFileUpload : boolean = false ,
81+ ) : string [ ] {
82+ const assets : string [ ] = [ ] ;
83+
84+ if ( buildTool . startsWith ( 'after-production-compile' ) ) {
85+ assets . push ( path . posix . join ( normalizedDistPath , getServerPattern ( { useDirectoryPath : true } ) ) ) ;
86+
87+ if ( buildTool === 'after-production-compile-turbopack' ) {
88+ // In turbopack we always want to upload the full static chunks directory
89+ // as the build output is not split into pages|app chunks
90+ assets . push ( path . posix . join ( normalizedDistPath , getStaticChunksPattern ( { useDirectoryPath : true } ) ) ) ;
91+ } else {
92+ // Webpack client builds in after-production-compile mode
93+ if ( widenClientFileUpload ) {
94+ assets . push ( path . posix . join ( normalizedDistPath , getStaticChunksPattern ( { useDirectoryPath : true } ) ) ) ;
95+ } else {
96+ assets . push (
97+ path . posix . join ( normalizedDistPath , getStaticChunksPagesPattern ( { useDirectoryPath : true } ) ) ,
98+ path . posix . join ( normalizedDistPath , getStaticChunksAppPattern ( { useDirectoryPath : true } ) ) ,
99+ ) ;
100+ }
101+ }
102+ } else {
103+ if ( buildTool === 'webpack-nodejs' || buildTool === 'webpack-edge' ) {
104+ // Server builds
105+ assets . push (
106+ path . posix . join ( normalizedDistPath , getServerPattern ( { useDirectoryPath : false } ) ) ,
107+ path . posix . join ( normalizedDistPath , FILE_PATTERNS . SERVERLESS ) ,
108+ ) ;
109+ } else if ( buildTool === 'webpack-client' ) {
110+ // Client builds
111+ if ( widenClientFileUpload ) {
112+ assets . push ( path . posix . join ( normalizedDistPath , getStaticChunksPattern ( { useDirectoryPath : false } ) ) ) ;
113+ } else {
114+ assets . push (
115+ path . posix . join ( normalizedDistPath , getStaticChunksPagesPattern ( { useDirectoryPath : false } ) ) ,
116+ path . posix . join ( normalizedDistPath , getStaticChunksAppPattern ( { useDirectoryPath : false } ) ) ,
117+ ) ;
118+ }
119+ }
120+ }
121+
122+ return assets ;
123+ }
124+
125+ /**
126+ * Creates ignore patterns for source map uploads
127+ */
128+ function createSourcemapUploadIgnorePattern (
129+ normalizedDistPath : string ,
130+ widenClientFileUpload : boolean = false ,
131+ ) : string [ ] {
132+ const ignore : string [ ] = [ ] ;
133+
134+ // We only add main-* files if the user has not opted into it
135+ if ( ! widenClientFileUpload ) {
136+ ignore . push ( path . posix . join ( normalizedDistPath , FILE_PATTERNS . MAIN_CHUNKS ) ) ;
137+ }
138+
139+ // Always ignore these patterns
140+ ignore . push (
141+ path . posix . join ( normalizedDistPath , FILE_PATTERNS . FRAMEWORK_CHUNKS ) ,
142+ path . posix . join ( normalizedDistPath , FILE_PATTERNS . FRAMEWORK_CHUNKS_DOT ) ,
143+ path . posix . join ( normalizedDistPath , FILE_PATTERNS . POLYFILLS_CHUNKS ) ,
144+ path . posix . join ( normalizedDistPath , FILE_PATTERNS . WEBPACK_CHUNKS ) ,
145+ ) ;
146+
147+ return ignore ;
148+ }
149+
150+ /**
151+ * Creates file patterns for deletion after source map upload
152+ */
153+ function createFilesToDeleteAfterUploadPattern (
154+ normalizedDistPath : string ,
155+ buildTool : BuildTool ,
156+ deleteSourcemapsAfterUpload : boolean ,
157+ useRunAfterProductionCompileHook : boolean = false ,
158+ ) : string [ ] | undefined {
159+ if ( ! deleteSourcemapsAfterUpload ) {
160+ return undefined ;
161+ }
162+
163+ // We don't want to delete source maps for server builds as this led to errors on Vercel in the past
164+ // See: https://github.com/getsentry/sentry-javascript/issues/13099
165+ if ( buildTool === 'webpack-nodejs' || buildTool === 'webpack-edge' ) {
166+ return undefined ;
167+ }
168+
169+ // Skip deletion for webpack client builds when using the experimental hook
170+ if ( buildTool === 'webpack-client' && useRunAfterProductionCompileHook ) {
171+ return undefined ;
172+ }
173+
174+ return SOURCEMAP_EXTENSIONS . map ( ext => path . posix . join ( normalizedDistPath , 'static' , '**' , ext ) ) ;
175+ }
176+
177+ /**
178+ * Determines if sourcemap uploads should be skipped
179+ */
180+ function shouldSkipSourcemapUpload ( buildTool : BuildTool , useRunAfterProductionCompileHook : boolean = false ) : boolean {
181+ return useRunAfterProductionCompileHook && buildTool . startsWith ( 'webpack' ) ;
182+ }
183+
184+ /**
185+ * Source rewriting function for webpack sources
186+ */
187+ function rewriteWebpackSources ( source : string ) : string {
188+ return source . replace ( / ^ w e b p a c k : \/ \/ (?: _ N _ E \/ ) ? / , '' ) ;
189+ }
190+
191+ /**
192+ * Creates release configuration
193+ */
194+ function createReleaseConfig (
195+ releaseName : string | undefined ,
196+ sentryBuildOptions : SentryBuildOptions ,
197+ ) : SentryBuildPluginOptions [ 'release' ] {
198+ if ( releaseName !== undefined ) {
199+ return {
200+ inject : false , // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead.
201+ name : releaseName ,
202+ create : sentryBuildOptions . release ?. create ,
203+ finalize : sentryBuildOptions . release ?. finalize ,
204+ dist : sentryBuildOptions . release ?. dist ,
205+ vcsRemote : sentryBuildOptions . release ?. vcsRemote ,
206+ setCommits : sentryBuildOptions . release ?. setCommits ,
207+ deploy : sentryBuildOptions . release ?. deploy ,
208+ ...sentryBuildOptions . unstable_sentryWebpackPluginOptions ?. release ,
209+ } ;
210+ }
211+
212+ return {
213+ inject : false ,
214+ create : false ,
215+ finalize : false ,
216+ } ;
217+ }
218+
219+ /**
220+ * Get Sentry Build Plugin options for both webpack and turbopack builds.
221+ * These options can be used in two ways:
222+ * 1. The options can be built in a single operation after the production build completes
223+ * 2. The options can be built in multiple operations, one for each webpack build
7224 */
8225export function getBuildPluginOptions ( {
9226 sentryBuildOptions,
10227 releaseName,
11228 distDirAbsPath,
229+ buildTool,
230+ useRunAfterProductionCompileHook,
12231} : {
13232 sentryBuildOptions : SentryBuildOptions ;
14233 releaseName : string | undefined ;
15234 distDirAbsPath : string ;
235+ buildTool : BuildTool ;
236+ useRunAfterProductionCompileHook ?: boolean ; // Whether the user has opted into using the experimental hook
16237} ) : SentryBuildPluginOptions {
17- const sourcemapUploadAssets : string [ ] = [ ] ;
18- const sourcemapUploadIgnore : string [ ] = [ ] ;
19-
20- const filesToDeleteAfterUpload : string [ ] = [ ] ;
21-
22238 // We need to convert paths to posix because Glob patterns use `\` to escape
23239 // glob characters. This clashes with Windows path separators.
24240 // See: https://www.npmjs.com/package/glob
25- const normalizedDistDirAbsPath = distDirAbsPath . replace ( / \\ / g , '/' ) ;
241+ const normalizedDistDirAbsPath = normalizePathForGlob ( distDirAbsPath ) ;
26242
27- sourcemapUploadAssets . push (
28- path . posix . join ( normalizedDistDirAbsPath , '**' ) , // Next.js build output
243+ const loggerPrefix = LOGGER_PREFIXES [ buildTool ] ;
244+ const widenClientFileUpload = sentryBuildOptions . widenClientFileUpload ?? false ;
245+ const deleteSourcemapsAfterUpload = sentryBuildOptions . sourcemaps ?. deleteSourcemapsAfterUpload ?? false ;
246+
247+ const sourcemapUploadAssets = createSourcemapUploadAssetPatterns (
248+ normalizedDistDirAbsPath ,
249+ buildTool ,
250+ widenClientFileUpload ,
29251 ) ;
30- if ( sentryBuildOptions . sourcemaps ?. deleteSourcemapsAfterUpload ) {
31- filesToDeleteAfterUpload . push (
32- path . posix . join ( normalizedDistDirAbsPath , '**' , '*.js.map' ) ,
33- path . posix . join ( normalizedDistDirAbsPath , '**' , '*.mjs.map' ) ,
34- path . posix . join ( normalizedDistDirAbsPath , '**' , '*.cjs.map' ) ,
35- ) ;
36- }
252+
253+ const sourcemapUploadIgnore = createSourcemapUploadIgnorePattern ( normalizedDistDirAbsPath , widenClientFileUpload ) ;
254+
255+ const filesToDeleteAfterUpload = createFilesToDeleteAfterUploadPattern (
256+ normalizedDistDirAbsPath ,
257+ buildTool ,
258+ deleteSourcemapsAfterUpload ,
259+ useRunAfterProductionCompileHook ,
260+ ) ;
261+
262+ const skipSourcemapsUpload = shouldSkipSourcemapUpload ( buildTool , useRunAfterProductionCompileHook ) ;
37263
38264 return {
39265 authToken : sentryBuildOptions . authToken ,
@@ -43,51 +269,28 @@ export function getBuildPluginOptions({
43269 telemetry : sentryBuildOptions . telemetry ,
44270 debug : sentryBuildOptions . debug ,
45271 errorHandler : sentryBuildOptions . errorHandler ,
46- reactComponentAnnotation : {
47- ...sentryBuildOptions . reactComponentAnnotation ,
48- ...sentryBuildOptions . unstable_sentryWebpackPluginOptions ?. reactComponentAnnotation ,
49- } ,
272+ reactComponentAnnotation : buildTool . startsWith ( 'after-production-compile' )
273+ ? undefined
274+ : {
275+ ...sentryBuildOptions . reactComponentAnnotation ,
276+ ...sentryBuildOptions . unstable_sentryWebpackPluginOptions ?. reactComponentAnnotation ,
277+ } ,
50278 silent : sentryBuildOptions . silent ,
51279 url : sentryBuildOptions . sentryUrl ,
52280 sourcemaps : {
53- disable : sentryBuildOptions . sourcemaps ?. disable ,
54- rewriteSources ( source ) {
55- if ( source . startsWith ( 'webpack://_N_E/' ) ) {
56- return source . replace ( 'webpack://_N_E/' , '' ) ;
57- } else if ( source . startsWith ( 'webpack://' ) ) {
58- return source . replace ( 'webpack://' , '' ) ;
59- } else {
60- return source ;
61- }
62- } ,
281+ disable : skipSourcemapsUpload ? true : ( sentryBuildOptions . sourcemaps ?. disable ?? false ) ,
282+ rewriteSources : rewriteWebpackSources ,
63283 assets : sentryBuildOptions . sourcemaps ?. assets ?? sourcemapUploadAssets ,
64284 ignore : sentryBuildOptions . sourcemaps ?. ignore ?? sourcemapUploadIgnore ,
65285 filesToDeleteAfterUpload,
66286 ...sentryBuildOptions . unstable_sentryWebpackPluginOptions ?. sourcemaps ,
67287 } ,
68- release :
69- releaseName !== undefined
70- ? {
71- inject : false , // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead.
72- name : releaseName ,
73- create : sentryBuildOptions . release ?. create ,
74- finalize : sentryBuildOptions . release ?. finalize ,
75- dist : sentryBuildOptions . release ?. dist ,
76- vcsRemote : sentryBuildOptions . release ?. vcsRemote ,
77- setCommits : sentryBuildOptions . release ?. setCommits ,
78- deploy : sentryBuildOptions . release ?. deploy ,
79- ...sentryBuildOptions . unstable_sentryWebpackPluginOptions ?. release ,
80- }
81- : {
82- inject : false ,
83- create : false ,
84- finalize : false ,
85- } ,
288+ release : createReleaseConfig ( releaseName , sentryBuildOptions ) ,
86289 bundleSizeOptimizations : {
87290 ...sentryBuildOptions . bundleSizeOptimizations ,
88291 } ,
89292 _metaOptions : {
90- loggerPrefixOverride : '[@sentry/nextjs]' ,
293+ loggerPrefixOverride : loggerPrefix ,
91294 telemetry : {
92295 metaFramework : 'nextjs' ,
93296 } ,
0 commit comments