11import { existsSync } from 'node:fs' ;
2+ import { basename } from 'node:path' ;
3+ import { pathToFileURL } from 'node:url' ;
24import { createResolver } from '@nuxt/kit' ;
35import { debug } from '@sentry/core' ;
46import * as fs from 'fs' ;
@@ -14,10 +16,21 @@ import {
1416 SENTRY_REEXPORTED_FUNCTIONS ,
1517 SENTRY_WRAPPED_ENTRY ,
1618 SENTRY_WRAPPED_FUNCTIONS ,
19+ toResolvablePath ,
1720} from './utils' ;
1821
1922const SERVER_CONFIG_FILENAME = 'sentry.server.config' ;
2023
24+ const CONFIG_EXTENSIONS = [ '.ts' , '.js' , '.mjs' , '.cjs' , '.mts' , '.cts' ] ;
25+
26+ function isServerConfigFile ( sourcePath : string , resolvedPath : string ) : boolean {
27+ if ( sourcePath === resolvedPath ) {
28+ return true ;
29+ }
30+ const name = basename ( sourcePath ) ;
31+ return name === SERVER_CONFIG_FILENAME || CONFIG_EXTENSIONS . some ( ext => name === `${ SERVER_CONFIG_FILENAME } ${ ext } ` ) ;
32+ }
33+
2134/**
2235 * Adds the `sentry.server.config.ts` file as `sentry.server.config.mjs` to the `.output` directory to be able to reference this file in the node --import option.
2336 *
@@ -115,7 +128,7 @@ export function addDynamicImportEntryFileWrapper(
115128
116129 nitro . options . rollupConfig . plugins . push (
117130 wrapEntryWithDynamicImport ( {
118- resolvedSentryConfigPath : createResolver ( nitro . options . rootDir ) . resolve ( `/ ${ serverConfigFile } ` ) ,
131+ resolvedSentryConfigPath : createResolver ( nitro . options . rootDir ) . resolve ( serverConfigFile ) ,
119132 experimental_entrypointWrappedFunctions : moduleOptions . experimental_entrypointWrappedFunctions ,
120133 } ) ,
121134 ) ;
@@ -131,7 +144,7 @@ function injectServerConfigPlugin(nitro: Nitro, serverConfigFile: string, isDebu
131144 name : 'rollup-plugin-inject-sentry-server-config' ,
132145
133146 buildStart ( ) {
134- const configPath = createResolver ( nitro . options . rootDir ) . resolve ( `/ ${ serverConfigFile } ` ) ;
147+ const configPath = createResolver ( nitro . options . rootDir ) . resolve ( serverConfigFile ) ;
135148
136149 if ( ! existsSync ( configPath ) ) {
137150 if ( isDebug ) {
@@ -151,7 +164,7 @@ function injectServerConfigPlugin(nitro: Nitro, serverConfigFile: string, isDebu
151164 resolveId ( source ) {
152165 if ( source . startsWith ( filePrefix ) ) {
153166 const originalFilePath = source . replace ( filePrefix , '' ) ;
154- const configPath = createResolver ( nitro . options . rootDir ) . resolve ( `/ ${ originalFilePath } ` ) ;
167+ const configPath = createResolver ( nitro . options . rootDir ) . resolve ( originalFilePath ) ;
155168
156169 return { id : configPath } ;
157170 }
@@ -164,8 +177,10 @@ function injectServerConfigPlugin(nitro: Nitro, serverConfigFile: string, isDebu
164177 * A Rollup plugin which wraps the server entry with a dynamic `import()`. This makes it possible to initialize Sentry first
165178 * by using a regular `import` and load the server after that.
166179 * This also works with serverless `handler` functions, as it re-exports the `handler`.
180+ *
181+ * Only exported for testing.
167182 */
168- function wrapEntryWithDynamicImport ( {
183+ export function wrapEntryWithDynamicImport ( {
169184 resolvedSentryConfigPath,
170185 experimental_entrypointWrappedFunctions,
171186 debug,
@@ -183,8 +198,16 @@ function wrapEntryWithDynamicImport({
183198 return {
184199 name : 'sentry-wrap-entry-with-dynamic-import' ,
185200 async resolveId ( source , importer , options ) {
186- if ( source . includes ( `/${ SERVER_CONFIG_FILENAME } ` ) ) {
187- return { id : source , moduleSideEffects : true } ;
201+ // `load()` emits `file://` specifiers because Node's ESM loader rejects bare Windows paths,
202+ // but Rollup's resolver only understands filesystem paths.
203+ const resolvable = toResolvablePath ( source ) ;
204+ if ( ! resolvable ) {
205+ return null ;
206+ }
207+ const { path : normalizedSource , wasFileUrl } = resolvable ;
208+
209+ if ( isServerConfigFile ( normalizedSource , resolvedSentryConfigPath ) ) {
210+ return { id : normalizedSource , moduleSideEffects : true } ;
188211 }
189212
190213 if ( source === 'import-in-the-middle/hook.mjs' ) {
@@ -195,8 +218,12 @@ function wrapEntryWithDynamicImport({
195218 return { id : source , moduleSideEffects : true , external : true } ;
196219 }
197220
198- if ( options . isEntry && source . includes ( '.mjs' ) && ! source . includes ( `.mjs${ SENTRY_WRAPPED_ENTRY } ` ) ) {
199- const resolution = await this . resolve ( source , importer , options ) ;
221+ if (
222+ options . isEntry &&
223+ normalizedSource . includes ( '.mjs' ) &&
224+ ! normalizedSource . includes ( `.mjs${ SENTRY_WRAPPED_ENTRY } ` )
225+ ) {
226+ const resolution = await this . resolve ( normalizedSource , importer , options ) ;
200227
201228 // If it cannot be resolved or is external, just return it so that Rollup can display an error
202229 if ( ! resolution || resolution ?. external ) return resolution ;
@@ -220,24 +247,36 @@ function wrapEntryWithDynamicImport({
220247 )
221248 . concat ( QUERY_END_INDICATOR ) } `;
222249 }
250+
251+ // Pass isEntry:false to avoid re-entering the isEntry branch and double-wrapping
252+ // (normalizedSource strips the SENTRY_WRAPPED_ENTRY query suffix).
253+ if ( wasFileUrl ) {
254+ const resolved = await this . resolve ( normalizedSource , importer , { ...options , isEntry : false } ) ;
255+ if ( resolved ) return resolved ;
256+ return { id : normalizedSource } ;
257+ }
258+
223259 return null ;
224260 } ,
225261 load ( id : string ) {
226262 if ( id . includes ( `.mjs${ SENTRY_WRAPPED_ENTRY } ` ) ) {
227263 const entryId = removeSentryQueryFromPath ( id ) . slice ( resolutionIdPrefix . length ) ;
264+ const entryIdUrl = pathToFileURL ( entryId ) . href ;
265+ const configUrl = pathToFileURL ( resolvedSentryConfigPath ) . href ;
228266
267+ // Use entryIdUrl so Node's runtime ESM loader receives file:// on Windows; Rollup normalizes it in resolveId.
229268 // Mostly useful for serverless `handler` functions
230269 const reExportedFunctions =
231270 id . includes ( SENTRY_WRAPPED_FUNCTIONS ) || id . includes ( SENTRY_REEXPORTED_FUNCTIONS )
232- ? constructFunctionReExport ( id , entryId )
271+ ? constructFunctionReExport ( id , entryIdUrl )
233272 : '' ;
234273
235274 return (
236275 // Regular `import` of the Sentry config
237- `import ${ JSON . stringify ( resolvedSentryConfigPath ) } ;\n` +
276+ `import ${ JSON . stringify ( configUrl ) } ;\n` +
238277 // Dynamic `import()` for the previous, actual entry point.
239278 // `import()` can be used for any code that should be run after the hooks are registered (https://nodejs.org/api/module.html#enabling)
240- `import(${ JSON . stringify ( entryId ) } );\n` +
279+ `import(${ JSON . stringify ( entryIdUrl ) } );\n` +
241280 // By importing "import-in-the-middle/hook.mjs", we can make sure this file wil be included, as not all node builders are including files imported with `module.register()`.
242281 "import 'import-in-the-middle/hook.mjs';\n" +
243282 `${ reExportedFunctions } \n`
0 commit comments