import { resolvePath, createResolver, useNuxt, addServerPlugin, addServerImports, defineNuxtModule, addPluginTemplate, addPlugin, addVitePlugin, addTemplate } from '@nuxt/kit'; import { consoleSandbox, debug } from '@sentry/core'; import * as path from 'path'; import { existsSync } from 'node:fs'; import * as fs from 'fs'; import { sentryRollupPlugin } from '@sentry/rollup-plugin'; import { sentryVitePlugin } from '@sentry/vite-plugin'; async function getNitroMajorVersion() { try { const { getPackageInfo } = await import('local-pkg'); const info = await getPackageInfo("nitro"); if (info?.version) { const major = parseInt(info.version.split(".")[0] ?? "2", 10); return isNaN(major) ? 2 : major; } } catch { } return 2; } async function findDefaultSdkInitFile(type, nuxt, options) { const possibleFileExtensions = ["ts", "js", "mjs", "cjs", "mts", "cts"]; const relativePaths = []; if (type === "server") { for (const ext of possibleFileExtensions) { relativePaths.push(`sentry.${type}.config.${ext}`); relativePaths.push(path.join("public", `instrument.${type}.${ext}`)); } } else { for (const ext of possibleFileExtensions) { relativePaths.push(`sentry.${type}.config.${ext}`); } } const layers = [...nuxt?.options._layers ?? []].reverse(); for (const layer of layers) { for (const relativePath of relativePaths) { const fullPath = path.resolve(layer.cwd, relativePath); if (fs.existsSync(fullPath)) { return fullPath; } } } const rootDir = options?.configDir ? await resolvePath(options.configDir, { type: "dir" }) : process.cwd(); for (const relativePath of relativePaths) { const fullPath = path.resolve(rootDir, relativePath); if (fs.existsSync(fullPath)) { return fullPath; } } return void 0; } function getFilenameFromNodeStartCommand(nodeCommand) { const regex = /[^/\\]+\.[^/\\]+$/; const match = nodeCommand.match(regex); return match ? match[0] : null; } const SENTRY_WRAPPED_ENTRY = "?sentry-query-wrapped-entry"; const SENTRY_WRAPPED_FUNCTIONS = "?sentry-query-wrapped-functions="; const SENTRY_REEXPORTED_FUNCTIONS = "?sentry-query-reexported-functions="; const QUERY_END_INDICATOR = "SENTRY-QUERY-END"; function removeSentryQueryFromPath(url) { const regex = new RegExp(`\\${SENTRY_WRAPPED_ENTRY}.*?\\${QUERY_END_INDICATOR}`); return url.replace(regex, ""); } function extractFunctionReexportQueryParameters(query) { const wrapRegex = new RegExp( `\\${SENTRY_WRAPPED_FUNCTIONS}(.*?)(\\${QUERY_END_INDICATOR}|\\${SENTRY_REEXPORTED_FUNCTIONS})` ); const reexportRegex = new RegExp(`\\${SENTRY_REEXPORTED_FUNCTIONS}(.*?)(\\${QUERY_END_INDICATOR})`); const wrapMatch = query.match(wrapRegex); const reexportMatch = query.match(reexportRegex); const wrap = wrapMatch?.[1]?.split(",").filter((param) => param !== "").map((str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) || []; const reexport = reexportMatch?.[1]?.split(",").filter((param) => param !== "" && param !== "default").map((str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) || []; return { wrap, reexport }; } function constructWrappedFunctionExportQuery(exportedBindings, entrypointWrappedFunctions, debug) { const functionsToExport = { wrap: [], reexport: [] }; Object.values(exportedBindings || {}).forEach( (functions) => functions.forEach((fn) => { if (entrypointWrappedFunctions.includes(fn)) { functionsToExport.wrap.push(fn); } else { functionsToExport.reexport.push(fn); } }) ); if (debug && functionsToExport.wrap.length === 0) { consoleSandbox( () => ( // eslint-disable-next-line no-console console.warn( "[Sentry] No functions found to wrap. In case the server needs to export async functions other than `handler` or `server`, consider adding the name(s) to Sentry's build options `sentry.experimental_entrypointWrappedFunctions` in `nuxt.config.ts`." ) ) ); } const wrapQuery = functionsToExport.wrap.length ? `${SENTRY_WRAPPED_FUNCTIONS}${functionsToExport.wrap.join(",")}` : ""; const reexportQuery = functionsToExport.reexport.length ? `${SENTRY_REEXPORTED_FUNCTIONS}${functionsToExport.reexport.join(",")}` : ""; return [wrapQuery, reexportQuery].join(""); } function constructFunctionReExport(pathWithQuery, entryId) { const { wrap: wrapFunctions, reexport: reexportFunctions } = extractFunctionReexportQueryParameters(pathWithQuery); return wrapFunctions.reduce( (functionsCode, currFunctionName) => functionsCode.concat( `async function ${currFunctionName}_sentryWrapped(...args) { const res = await import(${JSON.stringify(entryId)}); return res.${currFunctionName}.call(this, ...args); } export { ${currFunctionName}_sentryWrapped as ${currFunctionName} }; ` ), "" ).concat( reexportFunctions.reduce( (functionsCode, currFunctionName) => functionsCode.concat(`export { ${currFunctionName} } from ${JSON.stringify(entryId)};`), "" ) ); } function addOTelCommonJSImportAlias(nuxt, isNitroV3 = false) { if (!nuxt.options.dev || isNitroV3) { return; } if (!nuxt.options.alias) { nuxt.options.alias = {}; } if (!nuxt.options.alias["@opentelemetry/resources"]) { nuxt.options.alias["@opentelemetry/resources"] = "@opentelemetry/resources/build/src/index.js"; } } const SERVER_CONFIG_FILENAME = "sentry.server.config"; function addServerConfigToBuild(moduleOptions, nitro, serverConfigFile) { nitro.hooks.hook("rollup:before", (nitro2, rollupConfig) => { if (rollupConfig?.plugins === null || rollupConfig?.plugins === void 0) { rollupConfig.plugins = []; } else if (!Array.isArray(rollupConfig.plugins)) { rollupConfig.plugins = [rollupConfig.plugins]; } rollupConfig.plugins.push(injectServerConfigPlugin(nitro2, serverConfigFile, moduleOptions.debug)); }); } function addSentryTopImport(moduleOptions, nitro) { nitro.hooks.hook("close", async () => { const fileNameFromCommand = nitro.options.commands.preview && getFilenameFromNodeStartCommand(nitro.options.commands.preview); const presetsWithServerFile = ["netlify"]; const entryFileName = fileNameFromCommand ? fileNameFromCommand : typeof nitro.options.rollupConfig?.output.entryFileNames === "string" ? nitro.options.rollupConfig?.output.entryFileNames : presetsWithServerFile.includes(nitro.options.preset) ? "server.mjs" : "index.mjs"; const serverDirResolver = createResolver(nitro.options.output.serverDir); const entryFilePath = serverDirResolver.resolve(entryFileName); try { fs.readFile(entryFilePath, "utf8", (err, data) => { const updatedContent = `import './${SERVER_CONFIG_FILENAME}.mjs'; ${data}`; fs.writeFile(entryFilePath, updatedContent, "utf8", () => { if (moduleOptions.debug) { console.log( `[Sentry] Successfully added the Sentry import to the server entry file "\`${entryFilePath}\`"` ); } }); }); } catch (err) { if (moduleOptions.debug) { console.warn( `[Sentry] An error occurred when trying to add the Sentry import to the server entry file "\`${entryFilePath}\`":`, err ); } } }); } function addDynamicImportEntryFileWrapper(nitro, serverConfigFile, moduleOptions) { if (!nitro.options.rollupConfig) { nitro.options.rollupConfig = { output: {} }; } if (nitro.options.rollupConfig?.plugins === null || nitro.options.rollupConfig?.plugins === void 0) { nitro.options.rollupConfig.plugins = []; } else if (!Array.isArray(nitro.options.rollupConfig.plugins)) { nitro.options.rollupConfig.plugins = [nitro.options.rollupConfig.plugins]; } nitro.options.rollupConfig.plugins.push( wrapEntryWithDynamicImport({ resolvedSentryConfigPath: createResolver(nitro.options.rootDir).resolve(`/${serverConfigFile}`), experimental_entrypointWrappedFunctions: moduleOptions.experimental_entrypointWrappedFunctions }) ); } function injectServerConfigPlugin(nitro, serverConfigFile, isDebug) { const filePrefix = "\0virtual:sentry-server-config:"; return { name: "rollup-plugin-inject-sentry-server-config", buildStart() { const configPath = createResolver(nitro.options.rootDir).resolve(`/${serverConfigFile}`); if (!existsSync(configPath)) { if (isDebug) { debug.log(`[Sentry] Sentry server config file not found: ${configPath}`); } return; } this.emitFile({ type: "chunk", id: `${filePrefix}${serverConfigFile}`, fileName: `${SERVER_CONFIG_FILENAME}.mjs` }); }, resolveId(source) { if (source.startsWith(filePrefix)) { const originalFilePath = source.replace(filePrefix, ""); const configPath = createResolver(nitro.options.rootDir).resolve(`/${originalFilePath}`); return { id: configPath }; } return null; } }; } function wrapEntryWithDynamicImport({ resolvedSentryConfigPath, experimental_entrypointWrappedFunctions, debug: debug2 }) { const resolutionIdPrefix = "\0raw"; return { name: "sentry-wrap-entry-with-dynamic-import", async resolveId(source, importer, options) { if (source.includes(`/${SERVER_CONFIG_FILENAME}`)) { return { id: source, moduleSideEffects: true }; } if (source === "import-in-the-middle/hook.mjs") { return { id: source, moduleSideEffects: true, external: true }; } if (options.isEntry && source.includes(".mjs") && !source.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { const resolution = await this.resolve(source, importer, options); if (!resolution || resolution?.external) return resolution; const moduleInfo = await this.load(resolution); moduleInfo.moduleSideEffects = true; return resolution.id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`) ? resolution.id : `${resolutionIdPrefix}${resolution.id.concat(SENTRY_WRAPPED_ENTRY).concat( constructWrappedFunctionExportQuery( moduleInfo.exportedBindings, experimental_entrypointWrappedFunctions, debug2 ) ).concat(QUERY_END_INDICATOR)}`; } return null; }, load(id) { if (id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { const entryId = removeSentryQueryFromPath(id).slice(resolutionIdPrefix.length); const reExportedFunctions = id.includes(SENTRY_WRAPPED_FUNCTIONS) || id.includes(SENTRY_REEXPORTED_FUNCTIONS) ? constructFunctionReExport(id, entryId) : ""; return ( // Regular `import` of the Sentry config `import ${JSON.stringify(resolvedSentryConfigPath)}; import(${JSON.stringify(entryId)}); import 'import-in-the-middle/hook.mjs'; ${reExportedFunctions} ` ); } return null; } }; } function addServerTemplate(template) { const nuxt = useNuxt(); if (template.filename) { nuxt.options.nitro.virtual = nuxt.options.nitro.virtual || {}; nuxt.options.nitro.virtual[template.filename] = template.getContents; } return template; } function addDatabaseInstrumentation(nitro, isLegacyNitro, moduleOptions) { if (!nitro.experimental?.database) { moduleOptions?.debug && consoleSandbox(() => { console.log( "[Sentry] [Nitro Database Plugin]: No database configuration found. Skipping database instrumentation." ); }); return; } const databaseConfig = nitro.database || { default: {} }; addServerTemplate({ filename: "#sentry/database-config.mjs", getContents: () => { return `export const databaseConfig = ${JSON.stringify(databaseConfig)};`; } }); if (isLegacyNitro) { addServerPlugin(createResolver(import.meta.url).resolve("./runtime/plugins/database-legacy.server")); } else { addServerPlugin(createResolver(import.meta.url).resolve("./runtime/plugins/database.server")); } } function addMiddlewareImports() { addServerImports([ { name: "wrapMiddlewareHandlerWithSentry", from: createResolver(import.meta.url).resolve("./runtime/hooks/wrapMiddlewareHandler") } ]); } function addMiddlewareInstrumentation(nitro) { nitro.hooks.hook("rollup:before", (nitro2, rollupConfig) => { if (!rollupConfig.plugins) { rollupConfig.plugins = []; } if (!Array.isArray(rollupConfig.plugins)) { rollupConfig.plugins = [rollupConfig.plugins]; } rollupConfig.plugins.push(middlewareInstrumentationPlugin(nitro2)); }); } function middlewareInstrumentationPlugin(nitro) { const middlewareFiles = /* @__PURE__ */ new Set(); return { name: "sentry-nuxt-middleware-instrumentation", buildStart() { nitro.scannedHandlers?.forEach(({ middleware, handler }) => { if (middleware && handler) { middlewareFiles.add(handler); } }); }, transform(code, id) { if (middlewareFiles.has(id)) { const fileName = path.basename(id); return { code: wrapMiddlewareCode(code, fileName), map: null }; } return null; } }; } function wrapMiddlewareCode(originalCode, fileName) { const cleanFileName = fileName.replace(/\.(ts|js|mjs|mts|cts)$/, ""); return ` import { wrapMiddlewareHandlerWithSentry } from '#imports'; function defineInstrumentedEventHandler(handlerOrObject) { return defineEventHandler(wrapMiddlewareHandlerWithSentry(handlerOrObject, '${cleanFileName}')); } function instrumentedEventHandler(handlerOrObject) { return eventHandler(wrapMiddlewareHandlerWithSentry(handlerOrObject, '${cleanFileName}')); } function defineInstrumentedHandler(handlerOrObject) { return defineHandler(wrapMiddlewareHandlerWithSentry(handlerOrObject, '${cleanFileName}')); } ${originalCode.replace(/defineEventHandler\(/g, "defineInstrumentedEventHandler(").replace(/eventHandler\(/g, "instrumentedEventHandler(").replace(/defineHandler\(/g, "defineInstrumentedHandler(")} `; } function validateSourceMapsOptionsPlugin(options) { const { nuxt, moduleOptions, sourceMapsEnabled } = options; const isDebug = moduleOptions.debug; return { name: "sentry-nuxt-source-map-validation", config(viteConfig, env) { if (!sourceMapsEnabled || env.mode === "development" || nuxt.options?._prepare) { return; } const runtime = viteConfig.build?.ssr ? "server" : "client"; const nuxtSourceMapSetting = extractNuxtSourceMapSetting(nuxt, runtime); viteConfig.build = viteConfig.build || {}; const viteSourceMap = viteConfig.build.sourcemap; if (isDebug) { console.log(`[Sentry] Validating Vite config for the ${runtime} runtime.`); } validateDifferentSourceMapSettings({ nuxtSettingKey: `sourcemap.${runtime}`, nuxtSettingValue: nuxtSourceMapSetting, otherSettingKey: "viteConfig.build.sourcemap", otherSettingValue: viteSourceMap }); } }; } function setupSourceMaps(moduleOptions, nuxt, addVitePlugin) { const isDebug = moduleOptions.debug; const sourceMapsUploadOptions = moduleOptions.sourceMapsUploadOptions || {}; const sourceMapsEnabled = moduleOptions.sourcemaps?.disable === true ? false : moduleOptions.sourcemaps?.disable === false ? true : ( // eslint-disable-next-line deprecation/deprecation sourceMapsUploadOptions.enabled ?? true ); const shouldDeleteFilesFallback = { client: true, server: true }; nuxt.hook("modules:done", () => { if (sourceMapsEnabled && !nuxt.options.dev && !nuxt.options?._prepare) { const previousSourceMapSettings = changeNuxtSourceMapSettings(nuxt, moduleOptions); shouldDeleteFilesFallback.client = previousSourceMapSettings.client === "unset"; shouldDeleteFilesFallback.server = previousSourceMapSettings.server === "unset"; if (isDebug && (shouldDeleteFilesFallback.client || shouldDeleteFilesFallback.server)) { const enabledDeleteFallbacks = shouldDeleteFilesFallback.client && shouldDeleteFilesFallback.server ? "client-side and server-side" : shouldDeleteFilesFallback.server ? "server-side" : "client-side"; if (!moduleOptions.sourcemaps?.filesToDeleteAfterUpload && // eslint-disable-next-line deprecation/deprecation !sourceMapsUploadOptions.sourcemaps?.filesToDeleteAfterUpload) { console.log( `[Sentry] We enabled \`'hidden'\` source maps for your ${enabledDeleteFallbacks} build. Source map files will be automatically deleted after uploading them to Sentry.` ); } else { console.log( `[Sentry] We enabled \`'hidden'\` source maps for your ${enabledDeleteFallbacks} build. Source map files will be deleted according to your \`sourcemaps.filesToDeleteAfterUpload\` configuration. To use automatic deletion instead, leave \`filesToDeleteAfterUpload\` empty.` ); } } } }); if (sourceMapsEnabled && !nuxt.options.dev && !nuxt.options?._prepare) { addVitePlugin( [ validateSourceMapsOptionsPlugin({ nuxt, moduleOptions, sourceMapsEnabled }), // Vite plugin is added on the client and server side (plugin runs for both builds) ...sentryVitePlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback)) ], { dev: false, build: true } // Only add source map plugin during build ); } nuxt.hook("nitro:config", (nitroConfig) => { if (sourceMapsEnabled && !nitroConfig.dev && !nuxt.options?._prepare) { if (!nitroConfig.rollupConfig) { nitroConfig.rollupConfig = {}; } if (nitroConfig.rollupConfig.plugins === null || nitroConfig.rollupConfig.plugins === void 0) { nitroConfig.rollupConfig.plugins = []; } else if (!Array.isArray(nitroConfig.rollupConfig.plugins)) { nitroConfig.rollupConfig.plugins = [nitroConfig.rollupConfig.plugins]; } validateNitroSourceMapSettings(nuxt, nitroConfig, moduleOptions); if (isDebug) { console.log("[Sentry] Adding Sentry Rollup plugin to the server runtime."); } nitroConfig.rollupConfig.plugins.push( sentryRollupPlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback)) ); } }); } function normalizePath(path) { return path.replace(/^(\.\.\/)+/, "./"); } function getPluginOptions(moduleOptions, shouldDeleteFilesFallback) { const sourceMapsUploadOptions = moduleOptions.sourceMapsUploadOptions || {}; const shouldDeleteFilesAfterUpload = shouldDeleteFilesFallback?.client || shouldDeleteFilesFallback?.server; const fallbackFilesToDelete = [ ...shouldDeleteFilesFallback?.client ? [".*/**/public/**/*.map"] : [], ...shouldDeleteFilesFallback?.server ? [".*/**/server/**/*.map", ".*/**/output/**/*.map", ".*/**/function/**/*.map"] : [] ]; const sourcemapsOptions = moduleOptions.sourcemaps || {}; const deprecatedSourcemapsOptions = sourceMapsUploadOptions.sourcemaps || {}; const filesToDeleteAfterUpload = sourcemapsOptions.filesToDeleteAfterUpload ?? // eslint-disable-next-line deprecation/deprecation deprecatedSourcemapsOptions.filesToDeleteAfterUpload; if (typeof filesToDeleteAfterUpload === "undefined" && shouldDeleteFilesAfterUpload && moduleOptions.debug) { console.log( `[Sentry] Setting \`sentry.sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${fallbackFilesToDelete.map((path) => `"${path}"`).join(", ")}]\` to delete generated source maps after they were uploaded to Sentry.` ); } return { applicationKey: moduleOptions.applicationKey, // eslint-disable-next-line deprecation/deprecation org: moduleOptions.org ?? sourceMapsUploadOptions.org ?? process.env.SENTRY_ORG, // eslint-disable-next-line deprecation/deprecation project: moduleOptions.project ?? sourceMapsUploadOptions.project ?? process.env.SENTRY_PROJECT, // eslint-disable-next-line deprecation/deprecation authToken: moduleOptions.authToken ?? sourceMapsUploadOptions.authToken ?? process.env.SENTRY_AUTH_TOKEN, // eslint-disable-next-line deprecation/deprecation telemetry: moduleOptions.telemetry ?? sourceMapsUploadOptions.telemetry ?? true, // eslint-disable-next-line deprecation/deprecation url: moduleOptions.sentryUrl ?? sourceMapsUploadOptions.url ?? process.env.SENTRY_URL, headers: moduleOptions.headers, debug: moduleOptions.debug ?? false, // eslint-disable-next-line deprecation/deprecation silent: moduleOptions.silent ?? sourceMapsUploadOptions.silent ?? false, // eslint-disable-next-line deprecation/deprecation errorHandler: moduleOptions.errorHandler ?? sourceMapsUploadOptions.errorHandler, bundleSizeOptimizations: moduleOptions.bundleSizeOptimizations, // todo: test if this can be overridden by the user release: { // eslint-disable-next-line deprecation/deprecation name: moduleOptions.release?.name ?? sourceMapsUploadOptions.release?.name, // Support all release options from BuildTimeOptionsBase ...moduleOptions.release, ...moduleOptions?.unstable_sentryBundlerPluginOptions?.release }, _metaOptions: { telemetry: { metaFramework: "nuxt" } }, ...moduleOptions?.unstable_sentryBundlerPluginOptions, sourcemaps: { disable: moduleOptions.sourcemaps?.disable, // The server/client files are in different places depending on the nitro preset (e.g. '.output/server' or '.netlify/functions-internal/server') // We cannot determine automatically how the build folder looks like (depends on the preset), so we have to accept that source maps are uploaded multiple times (with the vitePlugin for Nuxt and the rollupPlugin for Nitro). // If we could know where the server/client assets are located, we could do something like this (based on the Nitro preset): isNitro ? ['./.output/server/**/*'] : ['./.output/public/**/*'], // eslint-disable-next-line deprecation/deprecation assets: sourcemapsOptions.assets ?? deprecatedSourcemapsOptions.assets ?? void 0, // eslint-disable-next-line deprecation/deprecation ignore: sourcemapsOptions.ignore ?? deprecatedSourcemapsOptions.ignore ?? void 0, filesToDeleteAfterUpload: filesToDeleteAfterUpload ? filesToDeleteAfterUpload : shouldDeleteFilesFallback?.server || shouldDeleteFilesFallback?.client ? fallbackFilesToDelete : void 0, rewriteSources: sourcemapsOptions.rewriteSources ?? normalizePath, ...moduleOptions?.unstable_sentryBundlerPluginOptions?.sourcemaps } }; } function extractNuxtSourceMapSetting(nuxt, runtime) { if (!runtime) { return void 0; } else { return typeof nuxt.options?.sourcemap === "boolean" || typeof nuxt.options?.sourcemap === "string" ? nuxt.options.sourcemap : nuxt.options?.sourcemap?.[runtime]; } } function changeNuxtSourceMapSettings(nuxt, sentryModuleOptions) { nuxt.options.sourcemap = nuxt.options.sourcemap ?? { server: void 0, client: void 0 }; let previousUserSourceMapSetting = { client: void 0, server: void 0 }; const nuxtSourceMap = nuxt.options.sourcemap; const isDebug = sentryModuleOptions.debug; if (typeof nuxtSourceMap === "string" || typeof nuxtSourceMap === "boolean" || typeof nuxtSourceMap === "undefined") { switch (nuxtSourceMap) { case false: warnExplicitlyDisabledSourceMap("sourcemap", isDebug); previousUserSourceMapSetting = { client: "disabled", server: "disabled" }; break; case "hidden": case true: logKeepEnabledSourceMapSetting(sentryModuleOptions, "sourcemap", nuxtSourceMap.toString()); previousUserSourceMapSetting = { client: "enabled", server: "enabled" }; break; case void 0: nuxt.options.sourcemap = { server: "hidden", client: "hidden" }; isDebug && logSentryEnablesSourceMap("sourcemap.client", "hidden"); isDebug && logSentryEnablesSourceMap("sourcemap.server", "hidden"); previousUserSourceMapSetting = { client: "unset", server: "unset" }; break; } } else { if (nuxtSourceMap.client === false) { warnExplicitlyDisabledSourceMap("sourcemap.client", isDebug); previousUserSourceMapSetting.client = "disabled"; } else if (["hidden", true].includes(nuxtSourceMap.client)) { logKeepEnabledSourceMapSetting(sentryModuleOptions, "sourcemap.client", nuxtSourceMap.client.toString()); previousUserSourceMapSetting.client = "enabled"; } else { nuxt.options.sourcemap.client = "hidden"; isDebug && logSentryEnablesSourceMap("sourcemap.client", "hidden"); previousUserSourceMapSetting.client = "unset"; } if (nuxtSourceMap.server === false) { warnExplicitlyDisabledSourceMap("sourcemap.server", isDebug); previousUserSourceMapSetting.server = "disabled"; } else if (["hidden", true].includes(nuxtSourceMap.server)) { logKeepEnabledSourceMapSetting(sentryModuleOptions, "sourcemap.server", nuxtSourceMap.server.toString()); previousUserSourceMapSetting.server = "enabled"; } else { nuxt.options.sourcemap.server = "hidden"; isDebug && logSentryEnablesSourceMap("sourcemap.server", "hidden"); previousUserSourceMapSetting.server = "unset"; } } return previousUserSourceMapSetting; } function validateNitroSourceMapSettings(nuxt, nitroConfig, sentryModuleOptions) { const isDebug = sentryModuleOptions.debug; const nuxtSourceMap = extractNuxtSourceMapSetting(nuxt, "server"); validateDifferentSourceMapSettings({ nuxtSettingKey: "sourcemap.server", nuxtSettingValue: nuxtSourceMap, otherSettingKey: "nitro.sourceMap", otherSettingValue: nitroConfig.sourceMap }); nitroConfig.rollupConfig = nitroConfig.rollupConfig || {}; nitroConfig.rollupConfig.output = nitroConfig.rollupConfig.output || { sourcemap: void 0 }; const nitroRollupSourceMap = nitroConfig.rollupConfig.output.sourcemap; if (typeof nitroRollupSourceMap !== "undefined" && ["hidden", "inline", true, false].includes(nitroRollupSourceMap)) { const settingKey = "nitro.rollupConfig.output.sourcemap"; validateDifferentSourceMapSettings({ nuxtSettingKey: "sourcemap.server", nuxtSettingValue: nuxtSourceMap, otherSettingKey: settingKey, otherSettingValue: nitroRollupSourceMap }); } nitroConfig.rollupConfig.output.sourcemapExcludeSources = false; if (isDebug) { console.log( "[Sentry] Set `sourcemapExcludeSources: false` in the Nuxt config (`nitro.rollupConfig.output`). Source maps will now include the actual code to be able to un-minify code snippets in Sentry." ); } } function validateDifferentSourceMapSettings({ nuxtSettingKey, nuxtSettingValue, otherSettingKey, otherSettingValue }) { if (nuxtSettingValue !== otherSettingValue) { console.warn( `[Sentry] Source map generation settings are conflicting. Sentry uses \`${nuxtSettingKey}: ${nuxtSettingValue}\`. However, a conflicting setting was discovered (\`${otherSettingKey}: ${otherSettingValue}\`). This setting was probably explicitly set in your configuration. Sentry won't override this setting but it may affect source maps generation and upload. Without source maps, code snippets on the Sentry Issues page will remain minified.` ); } } function logKeepEnabledSourceMapSetting(sentryNuxtModuleOptions, settingKey, settingValue) { if (sentryNuxtModuleOptions.debug) { console.log( `[Sentry] \`${settingKey}\` is enabled with \`${settingValue}\`. This will correctly un-minify the code snippet on the Sentry Issue Details page.` ); } } function warnExplicitlyDisabledSourceMap(settingKey, isDebug) { if (isDebug) { console.warn( `[Sentry] Source map generation is currently disabled in your Vite configuration (\`${settingKey}: false \`). This setting is either a default setting or was explicitly set in your configuration. Sentry won't override this setting. Without source maps, code snippets on the Sentry Issues page will remain minified. To show unminified code, enable source maps in \`${settingKey}\` (e.g. by setting them to \`hidden\`).` ); } else { console.warn(`[Sentry] Source map generation (\`${settingKey}\`) is disabled in your Vite configuration.`); } } function logSentryEnablesSourceMap(settingKey, settingValue) { console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: ${settingValue}\`.`); } function addStorageInstrumentation(nuxt, isLegacyNitro) { const moduleDirResolver = createResolver(import.meta.url); const userStorageMounts = Object.keys(nuxt.options.nitro.storage || {}); addServerTemplate({ filename: "#sentry/storage-config.mjs", getContents: () => { return `export const userStorageMounts = ${JSON.stringify(userStorageMounts)};`; } }); if (isLegacyNitro) { addServerPlugin(moduleDirResolver.resolve("./runtime/plugins/storage-legacy.server")); } else { addServerPlugin(moduleDirResolver.resolve("./runtime/plugins/storage.server")); } } const module = defineNuxtModule({ meta: { name: "@sentry/nuxt/module", configKey: "sentry", compatibility: { nuxt: ">=3.7.0" } }, defaults: {}, async setup(moduleOptionsParam, nuxt) { if (moduleOptionsParam?.enabled === false) { return; } const moduleOptions = { ...moduleOptionsParam, autoInjectServerSentry: moduleOptionsParam.autoInjectServerSentry, experimental_entrypointWrappedFunctions: moduleOptionsParam.experimental_entrypointWrappedFunctions || [ "default", "handler", "server" ] }; const moduleDirResolver = createResolver(import.meta.url); const buildDirResolver = createResolver(nuxt.options.buildDir); const clientConfigFile = await findDefaultSdkInitFile("client", nuxt, moduleOptions); if (clientConfigFile) { addPluginTemplate({ mode: "client", filename: "sentry-client-config.mjs", order: 0, // Dynamic import of config file to wrap it within a Nuxt context (here: defineNuxtPlugin) // Makes it possible to call useRuntimeConfig() in the user-defined sentry config file getContents: () => ` import { defineNuxtPlugin } from "#imports"; export default defineNuxtPlugin({ name: 'sentry-client-config', async setup() { await import("${buildDirResolver.resolve(`/${clientConfigFile}`)}") } });` }); addPlugin({ src: moduleDirResolver.resolve("./runtime/plugins/sentry.client"), mode: "client", order: 1 }); } const serverConfigFile = await findDefaultSdkInitFile("server", nuxt, moduleOptions); const isNitroV3 = await getNitroMajorVersion() >= 3; const nuxtMajor = parseInt(nuxt._version?.split(".")[0] ?? "3", 10); const isMinNuxtV4 = nuxtMajor >= 4; if (serverConfigFile) { if (isNitroV3) { addServerPlugin(moduleDirResolver.resolve("./runtime/plugins/handler.server")); addServerPlugin(moduleDirResolver.resolve("./runtime/plugins/update-route-name.server")); } else { addServerPlugin(moduleDirResolver.resolve("./runtime/plugins/handler-legacy.server")); addServerPlugin(moduleDirResolver.resolve("./runtime/plugins/update-route-name-legacy.server")); } addServerPlugin(moduleDirResolver.resolve("./runtime/plugins/sentry.server")); if (isMinNuxtV4) { addPlugin({ src: moduleDirResolver.resolve("./runtime/plugins/route-detector.server"), mode: "server" }); } else { addPlugin({ src: moduleDirResolver.resolve("./runtime/plugins/route-detector-legacy.server"), mode: "server" }); } addMiddlewareImports(); addStorageInstrumentation(nuxt, !isNitroV3); addDatabaseInstrumentation(nuxt.options.nitro, !isNitroV3, moduleOptions); } if (clientConfigFile || serverConfigFile) { setupSourceMaps(moduleOptions, nuxt, addVitePlugin); } addOTelCommonJSImportAlias(nuxt, isNitroV3); let pagesData = []; nuxt.hooks.hook("pages:extend", (pages) => { pagesData = pages.map((page) => ({ file: page.file, path: page.path })).filter((page) => { return page.path.includes(":") || page?.file?.includes("["); }); }); if (isMinNuxtV4) { const pagesDataVirtualModuleId = "#sentry/nuxt-pages-data.mjs"; addVitePlugin({ name: "sentry-nuxt-pages-data-virtual", resolveId: (id) => id === pagesDataVirtualModuleId ? `\0${pagesDataVirtualModuleId}` : null, load: (id) => id === `\0${pagesDataVirtualModuleId}` ? `export default ${JSON.stringify(pagesData, null, 2)};` : void 0 }); } else { addTemplate({ filename: "sentry--nuxt-pages-data.mjs", getContents: () => `export default ${JSON.stringify(pagesData, null, 2)};` }); } nuxt.hook("prepare:types", (options) => { const tsConfig = options.tsConfig; if (!tsConfig.include) { tsConfig.include = []; } if (clientConfigFile) { const relativePath = path.relative(nuxt.options.buildDir, clientConfigFile); tsConfig.include.push(relativePath); } if (serverConfigFile) { const relativePath = path.relative(nuxt.options.buildDir, serverConfigFile); tsConfig.include.push(relativePath); } }); nuxt.hooks.hook("nitro:init", (nitro) => { if (nuxt.options?._prepare) { return; } if (serverConfigFile) { addMiddlewareInstrumentation(nitro); } if (serverConfigFile?.includes(".server.config")) { consoleSandbox(() => { const serverDir = nitro.options.output.serverDir; if (serverDir.includes(".netlify") || !!process.env.NETLIFY) { console.warn( "[Sentry] Warning: The Sentry SDK detected a Netlify build. Server-side support for the Sentry Nuxt SDK on Netlify is currently unreliable due to technical limitations of serverless functions. Traces are not collected, and errors may occasionally not be reported. For more information on setting up Sentry on the Nuxt server-side, please refer to the documentation: https://docs.sentry.io/platforms/javascript/guides/nuxt/install/" ); } if (serverDir.includes(".vercel") || !!process.env.VERCEL) { console.warn( "[Sentry] Warning: The Sentry SDK detected a Vercel build. The Sentry Nuxt SDK currently does not support tracing on Vercel. For more information on setting up Sentry on the Nuxt server-side, please refer to the documentation: https://docs.sentry.io/platforms/javascript/guides/nuxt/install/" ); } }); if (moduleOptions.autoInjectServerSentry !== "experimental_dynamic-import") { addServerConfigToBuild(moduleOptions, nitro, serverConfigFile); if (moduleOptions.debug) { const serverDirResolver = createResolver(nitro.options.output.serverDir); const serverConfigPath = serverDirResolver.resolve("sentry.server.config.mjs"); const serverConfigRelativePath = `.${path.sep}${path.relative(nitro.options.rootDir, serverConfigPath)}`; consoleSandbox(() => { console.log( `[Sentry] Using \`${serverConfigFile}\` for server-side Sentry configuration. To activate Sentry on the Nuxt server-side, this file must be preloaded when starting your application. Make sure to add this where you deploy and/or run your application. Read more here: https://docs.sentry.io/platforms/javascript/guides/nuxt/install/.` ); if (nitro.options.dev) { console.log( `[Sentry] During development, preload Sentry with the NODE_OPTIONS environment variable: \`NODE_OPTIONS='--import ${serverConfigRelativePath}' nuxt dev\`. The file is generated in the build directory (usually '.nuxt'). If you delete the build directory, run \`nuxt dev\` to regenerate it.` ); } else { console.log( `[Sentry] When running your built application, preload Sentry via a command-line flag (\`node --import ${serverConfigRelativePath} [...]\`) or via an environment variable (\`NODE_OPTIONS='--import ${serverConfigRelativePath}' node [...]\`).` ); } }); } } if (moduleOptions.autoInjectServerSentry === "top-level-import") { addSentryTopImport(moduleOptions, nitro); } if (moduleOptions.autoInjectServerSentry === "experimental_dynamic-import") { addDynamicImportEntryFileWrapper(nitro, serverConfigFile, moduleOptions); if (moduleOptions.debug) { consoleSandbox(() => { console.log( "[Sentry] Wrapping the server entry file with a dynamic `import()`, so Sentry can be preloaded before the server initializes." ); }); } } } }); } }); export { module as default };