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 { basename } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import * as fs from 'fs'; import { INSTRUMENTED_MODULE_NAMES } from '@sentry/server-utils/orchestrion/config'; import { sentryOrchestrionPlugin } from '@sentry/server-utils/orchestrion/rollup'; import { sentryRollupPlugin } from '@sentry/rollup-plugin'; import { sentryVitePlugin } from '@sentry/vite-plugin'; import { createSentryBuildPluginManager } from '@sentry/bundler-plugins/core'; async function getNitroMajorVersion(rootDir) { try { const { getPackageInfo } = await import('local-pkg'); const fromPackage = (dir) => ({ paths: [path.join(dir, "package.json")] }); let provider = await getPackageInfo("nuxt", fromPackage(rootDir)); if (provider?.packageJson.dependencies?.["@nuxt/nitro-server"]) { provider = await getPackageInfo("@nuxt/nitro-server", fromPackage(provider.rootPath)) ?? provider; } if (!provider?.packageJson.dependencies?.nitro) { return 2; } const info = await getPackageInfo("nitro", fromPackage(provider.rootPath)); const major = parseInt(info?.version?.split(".")[0] ?? "", 10); return isNaN(major) ? 3 : 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 toResolvablePath(source) { if (!source.startsWith("file://")) { return { path: source, wasFileUrl: false }; } if (source === "file://" || source === "file:///") { return void 0; } try { const filePath = fileURLToPath(source); if (!filePath || filePath === "/" || filePath === "\\") { return void 0; } return { path: filePath, wasFileUrl: true }; } catch { return void 0; } } 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"; const CONFIG_EXTENSIONS = [".ts", ".js", ".mjs", ".cjs", ".mts", ".cts"]; function isServerConfigFile(sourcePath, resolvedPath) { if (sourcePath === resolvedPath) { return true; } const name = basename(sourcePath); return name === SERVER_CONFIG_FILENAME || CONFIG_EXTENSIONS.some((ext) => name === `${SERVER_CONFIG_FILENAME}${ext}`); } 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) { const resolvable = toResolvablePath(source); if (!resolvable) { return null; } const { path: normalizedSource, wasFileUrl } = resolvable; if (isServerConfigFile(normalizedSource, resolvedSentryConfigPath)) { return { id: normalizedSource, moduleSideEffects: true }; } if (source === "import-in-the-middle/hook.mjs") { return { id: source, moduleSideEffects: true, external: true }; } if (options.isEntry && normalizedSource.includes(".mjs") && !normalizedSource.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { const resolution = await this.resolve(normalizedSource, 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)}`; } if (wasFileUrl) { const resolved = await this.resolve(normalizedSource, importer, { ...options, isEntry: false }); if (resolved) return resolved; return { id: normalizedSource }; } return null; }, load(id) { if (id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { const entryId = removeSentryQueryFromPath(id).slice(resolutionIdPrefix.length); const entryIdUrl = pathToFileURL(entryId).href; const configUrl = pathToFileURL(resolvedSentryConfigPath).href; const reExportedFunctions = id.includes(SENTRY_WRAPPED_FUNCTIONS) || id.includes(SENTRY_REEXPORTED_FUNCTIONS) ? constructFunctionReExport(id, entryIdUrl) : ""; return ( // Regular `import` of the Sentry config `import ${JSON.stringify(configUrl)}; import(${JSON.stringify(entryIdUrl)}); 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(")} `; } const IORedisDependencies = ["standard-as-callback"]; function setupOrchestrion(nuxt) { nuxt.hook("nitro:config", (nitroConfig) => { if (nuxt.options?._prepare) { return; } 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]; } nitroConfig.rollupConfig.plugins.push(sentryOrchestrionPlugin()); const externals = nitroConfig.externals || (nitroConfig.externals = {}); const inline = externals.inline; const existingInline = Array.isArray(inline) ? inline : inline ? [inline] : []; externals.inline = [.../* @__PURE__ */ new Set([...existingInline, ...INSTRUMENTED_MODULE_NAMES, ...IORedisDependencies])]; }); } function withoutSourceMapDeletion(options) { return { ...options, sourcemaps: { ...options.sourcemaps, filesToDeleteAfterUpload: void 0 } }; } async function deleteSourceMapsAfterBuild(options) { const filesToDeleteAfterUpload = await options.sourcemaps?.filesToDeleteAfterUpload; if (filesToDeleteAfterUpload === void 0) { return; } const deletionOptions = { ...options, sourcemaps: { ...options.sourcemaps, filesToDeleteAfterUpload } }; const sentryBuildPluginManager = createSentryBuildPluginManager(deletionOptions, { buildTool: "nuxt", loggerPrefix: "[Sentry Nuxt]" }); await sentryBuildPluginManager.deleteArtifacts(); } 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 typescript/no-deprecated 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 typescript/no-deprecated !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 })], { dev: false, build: true }); addVitePlugin( sentryVitePlugin(withoutSourceMapDeletion(getPluginOptions(moduleOptions, shouldDeleteFilesFallback))), // Nitro bundles the SSR output again, so debug IDs from the SSR build would point to the wrong maps. { dev: false, build: true, server: false } ); } nuxt.hook("nitro:config", (nitroConfig) => { if (sourceMapsEnabled && !nitroConfig.dev && !nuxt.options?._prepare) { 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(withoutSourceMapDeletion(getPluginOptions(moduleOptions, shouldDeleteFilesFallback))) ); } }); nuxt.hook("close", async () => { if (sourceMapsEnabled && !nuxt.options.dev && !nuxt.options?._prepare) { await deleteSourceMapsAfterBuild(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 typescript/no-deprecated 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 typescript/no-deprecated org: moduleOptions.org ?? sourceMapsUploadOptions.org ?? process.env.SENTRY_ORG, // eslint-disable-next-line typescript/no-deprecated project: moduleOptions.project ?? sourceMapsUploadOptions.project ?? process.env.SENTRY_PROJECT, // eslint-disable-next-line typescript/no-deprecated authToken: moduleOptions.authToken ?? sourceMapsUploadOptions.authToken ?? process.env.SENTRY_AUTH_TOKEN, // eslint-disable-next-line typescript/no-deprecated telemetry: moduleOptions.telemetry ?? sourceMapsUploadOptions.telemetry ?? true, // eslint-disable-next-line typescript/no-deprecated url: moduleOptions.sentryUrl ?? sourceMapsUploadOptions.url ?? process.env.SENTRY_URL, headers: moduleOptions.headers, debug: moduleOptions.debug ?? false, // eslint-disable-next-line typescript/no-deprecated silent: moduleOptions.silent ?? sourceMapsUploadOptions.silent ?? false, // eslint-disable-next-line typescript/no-deprecated errorHandler: moduleOptions.errorHandler ?? sourceMapsUploadOptions.errorHandler, bundleSizeOptimizations: moduleOptions.bundleSizeOptimizations, // todo: test if this can be overridden by the user release: { // eslint-disable-next-line typescript/no-deprecated 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 each plugin uploads its own build output (the vitePlugin for the Nuxt client 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 typescript/no-deprecated assets: sourcemapsOptions.assets ?? deprecatedSourcemapsOptions.assets ?? void 0, // eslint-disable-next-line typescript/no-deprecated 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")); } } var module$1 = 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(nuxt.options.rootDir) >= 3; const nuxtMajor = parseInt(nuxt._version?.split(".")[0] ?? "3", 10); const isMinNuxtV4 = nuxtMajor >= 4; if (serverConfigFile) { if (moduleOptions._experimental?.useDiagnosticsChannelInjection) { setupOrchestrion(nuxt); } 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$1 as default };