From a6924a1e3305ba311d1ac3d60e0cd6587353f7ed Mon Sep 17 00:00:00 2001 From: Julianne Koenig Date: Mon, 8 Jul 2024 18:19:52 -0400 Subject: [PATCH] #noissue add dist folder back into project --- .gitignore | 2 +- dist/module.cjs | 5 + dist/module.d.mts | 178 ++++++++ dist/module.d.ts | 178 ++++++++ dist/module.json | 5 + dist/module.mjs | 766 ++++++++++++++++++++++++++++++++ dist/templates/client.shared.js | 92 ++++ dist/templates/options.ejs | 7 + dist/templates/plugin.client.js | 10 + dist/templates/plugin.lazy.js | 172 +++++++ dist/templates/plugin.mocked.js | 15 + dist/templates/plugin.server.js | 46 ++ dist/types.d.mts | 18 + dist/types.d.ts | 18 + 14 files changed, 1511 insertions(+), 1 deletion(-) create mode 100644 dist/module.cjs create mode 100644 dist/module.d.mts create mode 100644 dist/module.d.ts create mode 100644 dist/module.json create mode 100644 dist/module.mjs create mode 100644 dist/templates/client.shared.js create mode 100644 dist/templates/options.ejs create mode 100644 dist/templates/plugin.client.js create mode 100644 dist/templates/plugin.lazy.js create mode 100644 dist/templates/plugin.mocked.js create mode 100644 dist/templates/plugin.server.js create mode 100644 dist/types.d.mts create mode 100644 dist/types.d.ts diff --git a/.gitignore b/.gitignore index 49940026..9e1df016 100755 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ node_modules **/.yarn/*state* # Generated dirs -dist +# dist # Nuxt .nuxt diff --git a/dist/module.cjs b/dist/module.cjs new file mode 100644 index 00000000..5a320cb8 --- /dev/null +++ b/dist/module.cjs @@ -0,0 +1,5 @@ +module.exports = function(...args) { + return import('./module.mjs').then(m => m.default.call(this, ...args)) +} +const _meta = module.exports.meta = require('./module.json') +module.exports.getMeta = () => Promise.resolve(_meta) diff --git a/dist/module.d.mts b/dist/module.d.mts new file mode 100644 index 00000000..d9aba96f --- /dev/null +++ b/dist/module.d.mts @@ -0,0 +1,178 @@ +import { IntegrationFn, Options } from '@sentry/types'; +import { captureConsoleIntegration, contextLinesIntegration, debugIntegration, dedupeIntegration, extraErrorDataIntegration, httpClientIntegration, reportingObserverIntegration, rewriteFramesIntegration, sessionTimingIntegration, browserTracingIntegration, vueIntegration } from '@sentry/vue'; +import { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; +import * as SentryNode from '@sentry/node'; +import { anrIntegration, consoleIntegration, nodeContextIntegration, contextLinesIntegration as contextLinesIntegration$1, expressIntegration, graphqlIntegration, hapiIntegration, httpIntegration, localVariablesIntegration, modulesIntegration, mongoIntegration, mysqlIntegration, onUncaughtExceptionIntegration, onUnhandledRejectionIntegration, postgresIntegration, prismaIntegration, spotlightIntegration, nativeNodeFetchIntegration, NodeOptions } from '@sentry/node'; +import { Configuration } from 'webpack'; +import { breadcrumbsIntegration, globalHandlersIntegration, httpContextIntegration, replayIntegration, browserApiErrorsIntegration } from '@sentry/browser'; +import * as SentryTypes from '@sentry/core'; +import { functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration, requestDataIntegration } from '@sentry/core'; + +type IntegrationConfig = Parameters[0] | Record | false + +type BrowserIntegrations = { + Breadcrumbs?: IntegrationConfig + GlobalHandlers?: IntegrationConfig + HttpContext?: IntegrationConfig + Replay?: IntegrationConfig + TryCatch?: IntegrationConfig + CaptureConsole?: IntegrationConfig + ContextLines?: IntegrationConfig + Debug?: IntegrationConfig + Dedupe?: IntegrationConfig + ExtraErrorData?: IntegrationConfig + HttpClient?: IntegrationConfig + ReportingObserver?: IntegrationConfig + RewriteFrames?: IntegrationConfig + SessionTiming?: IntegrationConfig +} + +type CoreIntegrations = { + FunctionToString?: IntegrationConfig + InboundFilters?: IntegrationConfig + LinkedErrors?: IntegrationConfig + RequestData?: IntegrationConfig +} + +type NodeProfilingIntegrations = { + ProfilingIntegration?: IntegrationConfig // Dummy type since we don't want to depend on `@sentry/profiling-node` +} + +type NodeIntegrations = { + Anr?: IntegrationConfig + Console?: IntegrationConfig + Context?: IntegrationConfig + ContextLines?: IntegrationConfig + Express?: IntegrationConfig + GraphQL?: IntegrationConfig + Hapi?: IntegrationConfig + Http?: IntegrationConfig + LocalVariables?: IntegrationConfig + Modules?: IntegrationConfig + Mongo?: IntegrationConfig + Mysql?: IntegrationConfig + OnUncaughtException?: IntegrationConfig + OnUnhandledRejection?: IntegrationConfig + Postgres?: IntegrationConfig + Prisma?: IntegrationConfig + Spotlight?: IntegrationConfig + Undici?: IntegrationConfig + CaptureConsole?: IntegrationConfig + Debug?: IntegrationConfig + Dedupe?: IntegrationConfig + ExtraErrorData?: IntegrationConfig + HttpClient?: IntegrationConfig + ReportingObserver?: IntegrationConfig + RewriteFrames?: IntegrationConfig + SessionTiming?: IntegrationConfig +} + +type ClientCoreIntegrations = Pick +type ClientIntegrations = ClientCoreIntegrations & BrowserIntegrations + +type ServerCoreIntegrations = CoreIntegrations +type ServerIntegrations = ServerCoreIntegrations & NodeProfilingIntegrations & NodeIntegrations + +interface LazyConfiguration { + chunkName?: string + injectLoadHook?: boolean + injectMock?: boolean + mockApiMethods?: boolean | string[] + webpackPrefetch?: boolean + webpackPreload?: boolean +} + +interface TracingConfiguration extends Pick { + browserTracing?: Parameters[0] + vueOptions?: Partial>[0] +} + +interface ModuleConfiguration { + // Comment out and type with 'any' for now - '@sentry/vue/build/types/types' does not exist + // clientConfig: Partial | string + clientConfig: Record | string + clientIntegrations: ClientIntegrations + config: Options + customClientIntegrations: string + customServerIntegrations: string + disableClientRelease: boolean + disableClientSide: boolean + disabled: boolean + disableServerRelease: boolean + disableServerSide: boolean + dsn: string + tracing: boolean | TracingConfiguration + initialize: boolean + lazy: boolean | LazyConfiguration + logMockCalls: boolean + /** See available options at https://docs.sentry.io/platforms/node/sourcemaps/uploading/webpack/ */ + publishRelease: boolean | SentryWebpackPluginOptions + runtimeConfigKey: string + serverConfig: NodeOptions | string + serverIntegrations: ServerIntegrations + sourceMapStyle: Configuration['devtool'] +} + +type PartialModuleConfiguration = Partial + +type ModulePublicRuntimeConfig = Pick + +type Sentry = typeof SentryTypes +type NodeSentry = typeof SentryNode + +// add type to Vue context +declare module 'vue/types/vue' { + interface Vue { + readonly $sentry: Sentry + $sentryLoad(): Promise + $sentryReady(): Promise + } +} + +// App Context and NuxtAppOptions +declare module '@nuxt/types' { + interface Context { + readonly $sentry: Sentry + $sentryLoad(): Promise + $sentryReady(): Promise + } + + interface NuxtOptions { + sentry?: PartialModuleConfiguration + } + + interface NuxtAppOptions { + readonly $sentry: Sentry + $sentryLoad(): Promise + $sentryReady(): Promise + } +} + +declare module '@nuxt/types/config/runtime' { + interface NuxtRuntimeConfig { + sentry?: ModulePublicRuntimeConfig + } +} + +// add types for Vuex Store +declare module 'vuex/types' { + interface Store { + readonly $sentry: Sentry + $sentryLoad(): Promise + $sentryReady(): Promise + } +} + +declare global { + namespace NodeJS { + interface Process { + sentry: NodeSentry + } + } +} + +type ModuleOptions = PartialModuleConfiguration + +declare const _default: unknown; + +export { type ModuleOptions, type ModulePublicRuntimeConfig, _default as default }; diff --git a/dist/module.d.ts b/dist/module.d.ts new file mode 100644 index 00000000..d9aba96f --- /dev/null +++ b/dist/module.d.ts @@ -0,0 +1,178 @@ +import { IntegrationFn, Options } from '@sentry/types'; +import { captureConsoleIntegration, contextLinesIntegration, debugIntegration, dedupeIntegration, extraErrorDataIntegration, httpClientIntegration, reportingObserverIntegration, rewriteFramesIntegration, sessionTimingIntegration, browserTracingIntegration, vueIntegration } from '@sentry/vue'; +import { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; +import * as SentryNode from '@sentry/node'; +import { anrIntegration, consoleIntegration, nodeContextIntegration, contextLinesIntegration as contextLinesIntegration$1, expressIntegration, graphqlIntegration, hapiIntegration, httpIntegration, localVariablesIntegration, modulesIntegration, mongoIntegration, mysqlIntegration, onUncaughtExceptionIntegration, onUnhandledRejectionIntegration, postgresIntegration, prismaIntegration, spotlightIntegration, nativeNodeFetchIntegration, NodeOptions } from '@sentry/node'; +import { Configuration } from 'webpack'; +import { breadcrumbsIntegration, globalHandlersIntegration, httpContextIntegration, replayIntegration, browserApiErrorsIntegration } from '@sentry/browser'; +import * as SentryTypes from '@sentry/core'; +import { functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration, requestDataIntegration } from '@sentry/core'; + +type IntegrationConfig = Parameters[0] | Record | false + +type BrowserIntegrations = { + Breadcrumbs?: IntegrationConfig + GlobalHandlers?: IntegrationConfig + HttpContext?: IntegrationConfig + Replay?: IntegrationConfig + TryCatch?: IntegrationConfig + CaptureConsole?: IntegrationConfig + ContextLines?: IntegrationConfig + Debug?: IntegrationConfig + Dedupe?: IntegrationConfig + ExtraErrorData?: IntegrationConfig + HttpClient?: IntegrationConfig + ReportingObserver?: IntegrationConfig + RewriteFrames?: IntegrationConfig + SessionTiming?: IntegrationConfig +} + +type CoreIntegrations = { + FunctionToString?: IntegrationConfig + InboundFilters?: IntegrationConfig + LinkedErrors?: IntegrationConfig + RequestData?: IntegrationConfig +} + +type NodeProfilingIntegrations = { + ProfilingIntegration?: IntegrationConfig // Dummy type since we don't want to depend on `@sentry/profiling-node` +} + +type NodeIntegrations = { + Anr?: IntegrationConfig + Console?: IntegrationConfig + Context?: IntegrationConfig + ContextLines?: IntegrationConfig + Express?: IntegrationConfig + GraphQL?: IntegrationConfig + Hapi?: IntegrationConfig + Http?: IntegrationConfig + LocalVariables?: IntegrationConfig + Modules?: IntegrationConfig + Mongo?: IntegrationConfig + Mysql?: IntegrationConfig + OnUncaughtException?: IntegrationConfig + OnUnhandledRejection?: IntegrationConfig + Postgres?: IntegrationConfig + Prisma?: IntegrationConfig + Spotlight?: IntegrationConfig + Undici?: IntegrationConfig + CaptureConsole?: IntegrationConfig + Debug?: IntegrationConfig + Dedupe?: IntegrationConfig + ExtraErrorData?: IntegrationConfig + HttpClient?: IntegrationConfig + ReportingObserver?: IntegrationConfig + RewriteFrames?: IntegrationConfig + SessionTiming?: IntegrationConfig +} + +type ClientCoreIntegrations = Pick +type ClientIntegrations = ClientCoreIntegrations & BrowserIntegrations + +type ServerCoreIntegrations = CoreIntegrations +type ServerIntegrations = ServerCoreIntegrations & NodeProfilingIntegrations & NodeIntegrations + +interface LazyConfiguration { + chunkName?: string + injectLoadHook?: boolean + injectMock?: boolean + mockApiMethods?: boolean | string[] + webpackPrefetch?: boolean + webpackPreload?: boolean +} + +interface TracingConfiguration extends Pick { + browserTracing?: Parameters[0] + vueOptions?: Partial>[0] +} + +interface ModuleConfiguration { + // Comment out and type with 'any' for now - '@sentry/vue/build/types/types' does not exist + // clientConfig: Partial | string + clientConfig: Record | string + clientIntegrations: ClientIntegrations + config: Options + customClientIntegrations: string + customServerIntegrations: string + disableClientRelease: boolean + disableClientSide: boolean + disabled: boolean + disableServerRelease: boolean + disableServerSide: boolean + dsn: string + tracing: boolean | TracingConfiguration + initialize: boolean + lazy: boolean | LazyConfiguration + logMockCalls: boolean + /** See available options at https://docs.sentry.io/platforms/node/sourcemaps/uploading/webpack/ */ + publishRelease: boolean | SentryWebpackPluginOptions + runtimeConfigKey: string + serverConfig: NodeOptions | string + serverIntegrations: ServerIntegrations + sourceMapStyle: Configuration['devtool'] +} + +type PartialModuleConfiguration = Partial + +type ModulePublicRuntimeConfig = Pick + +type Sentry = typeof SentryTypes +type NodeSentry = typeof SentryNode + +// add type to Vue context +declare module 'vue/types/vue' { + interface Vue { + readonly $sentry: Sentry + $sentryLoad(): Promise + $sentryReady(): Promise + } +} + +// App Context and NuxtAppOptions +declare module '@nuxt/types' { + interface Context { + readonly $sentry: Sentry + $sentryLoad(): Promise + $sentryReady(): Promise + } + + interface NuxtOptions { + sentry?: PartialModuleConfiguration + } + + interface NuxtAppOptions { + readonly $sentry: Sentry + $sentryLoad(): Promise + $sentryReady(): Promise + } +} + +declare module '@nuxt/types/config/runtime' { + interface NuxtRuntimeConfig { + sentry?: ModulePublicRuntimeConfig + } +} + +// add types for Vuex Store +declare module 'vuex/types' { + interface Store { + readonly $sentry: Sentry + $sentryLoad(): Promise + $sentryReady(): Promise + } +} + +declare global { + namespace NodeJS { + interface Process { + sentry: NodeSentry + } + } +} + +type ModuleOptions = PartialModuleConfiguration + +declare const _default: unknown; + +export { type ModuleOptions, type ModulePublicRuntimeConfig, _default as default }; diff --git a/dist/module.json b/dist/module.json new file mode 100644 index 00000000..6a062725 --- /dev/null +++ b/dist/module.json @@ -0,0 +1,5 @@ +{ + "name": "@nuxtjs/sentry", + "configKey": "sentry", + "version": "8.0.7" +} \ No newline at end of file diff --git a/dist/module.mjs b/dist/module.mjs new file mode 100644 index 00000000..24c2a7f4 --- /dev/null +++ b/dist/module.mjs @@ -0,0 +1,766 @@ +import { fileURLToPath } from 'node:url'; +import { defu } from 'defu'; +import { resolvePath } from 'mlly'; +import * as Sentry from '@sentry/node'; +import { withScope, captureException } from '@sentry/node'; +import { existsSync } from 'node:fs'; +import { consola } from 'consola'; +import hash from 'hash-sum'; +import { parse, basename, resolve, normalize, relative } from 'pathe'; +import { resolveAlias as resolveAlias$1 } from 'pathe/utils'; +import { fileURLToPath as fileURLToPath$1 } from 'url'; +import { resolve as resolve$1 } from 'path'; +import initJiti from 'jiti'; +import * as SentryCore from '@sentry/core'; + +const nuxtCtx = { + value: null +}; +function useNuxt() { + const instance = nuxtCtx.value; + if (!instance) { + throw new Error("Nuxt instance is unavailable!"); + } + return instance; +} +function tryUseNuxt() { + return nuxtCtx.value; +} +const NUXT2_SHIMS_KEY = "__nuxt2_shims_sentry_key__"; +function nuxt2Shims(nuxt) { + if (nuxt[NUXT2_SHIMS_KEY]) { + return; + } + nuxt[NUXT2_SHIMS_KEY] = true; + nuxt.hooks = nuxt; + if (!nuxtCtx.value) { + nuxtCtx.value = nuxt; + nuxt.hook("close", () => { + nuxtCtx.value = null; + }); + } +} +function defineNuxtModule(definition) { + if (!definition.meta) { + definition.meta = {}; + } + if (definition.meta.configKey === void 0) { + definition.meta.configKey = definition.meta.name; + } + function getOptions(inlineOptions) { + const nuxt = useNuxt(); + const configKey = definition.meta.configKey || definition.meta.name; + const _defaults = definition.defaults instanceof Function ? definition.defaults(nuxt) : definition.defaults; + const _options = defu(inlineOptions, nuxt.options[configKey], _defaults); + return Promise.resolve(_options); + } + async function normalizedModule(inlineOptions) { + const nuxt = this.nuxt; + const uniqueKey = definition.meta.name || definition.meta.configKey; + if (uniqueKey) { + nuxt.options._requiredModules = nuxt.options._requiredModules || {}; + if (nuxt.options._requiredModules[uniqueKey]) { + return false; + } + nuxt.options._requiredModules[uniqueKey] = true; + } + nuxt2Shims(nuxt); + const _options = await getOptions(inlineOptions); + const res = await definition.setup?.call(null, _options, nuxt) ?? {}; + return defu(res, {}); + } + normalizedModule.getMeta = () => Promise.resolve(definition.meta); + normalizedModule.getOptions = getOptions; + return normalizedModule; +} +const logger$1 = consola; +function useLogger(tag) { + return tag ? logger$1.withTag(tag) : logger$1; +} +function resolveAlias(path, alias) { + if (!alias) { + alias = tryUseNuxt()?.options.alias || {}; + } + return resolveAlias$1(path, alias); +} +function normalizePlugin(plugin) { + if (typeof plugin === "string") { + plugin = { src: plugin }; + } else { + plugin = { ...plugin }; + } + if (!plugin.src) { + throw new Error("Invalid plugin. src option is required: " + JSON.stringify(plugin)); + } + const nonTopLevelPlugin = plugin.src.match(/\/plugins\/[^/]+\/index\.[^/]+$/i); + if (nonTopLevelPlugin && nonTopLevelPlugin.length > 0 && !useNuxt().options.plugins.find((i) => (typeof i === "string" ? i : i.src).endsWith(nonTopLevelPlugin[0]))) { + console.warn(`[warn] [nuxt] [deprecation] You are using a plugin that is within a subfolder of your plugins directory without adding it to your config explicitly. You can move it to the top-level plugins directory, or include the file '~${nonTopLevelPlugin[0]}' in your plugins config (https://nuxt.com/docs/api/configuration/nuxt-config#plugins-1) to remove this warning.`); + } + plugin.src = normalize(resolveAlias(plugin.src)); + if (plugin.ssr) { + plugin.mode = "server"; + } + if (!plugin.mode) { + const [, mode = "all"] = plugin.src.match(/\.(server|client)(\.\w+)*$/) || []; + plugin.mode = mode; + } + return plugin; +} +function addPlugin(_plugin, opts = {}) { + const nuxt = useNuxt(); + const plugin = normalizePlugin(_plugin); + nuxt.options.plugins = nuxt.options.plugins.filter((p) => normalizePlugin(p).src !== plugin.src); + nuxt.options.plugins[opts.append ? "push" : "unshift"](plugin); + return plugin; +} +function addPluginTemplate(plugin, opts = {}) { + const normalizedPlugin = typeof plugin === "string" ? { src: plugin } : { ...plugin, src: addTemplate(plugin).dst }; + return addPlugin(normalizedPlugin, opts); +} +function addTemplate(_template) { + const nuxt = useNuxt(); + const template = normalizeTemplate(_template); + nuxt.options.build.templates = nuxt.options.build.templates.filter((p) => normalizeTemplate(p).filename !== template.filename); + nuxt.options.build.templates.push(template); + return template; +} +function normalizeTemplate(template) { + if (!template) { + throw new Error("Invalid template: " + JSON.stringify(template)); + } + if (typeof template === "string") { + template = { src: template }; + } else { + template = { ...template }; + } + if (template.src) { + if (!existsSync(template.src)) { + throw new Error("Template not found: " + template.src); + } + if (!template.filename) { + const srcPath = parse(template.src); + template.filename = template.fileName || `${basename(srcPath.dir)}.${srcPath.name}.${hash(template.src)}${srcPath.ext}`; + } + } + if (!template.src && !template.getContents) { + throw new Error("Invalid template. Either getContents or src options should be provided: " + JSON.stringify(template)); + } + if (!template.filename) { + throw new Error("Invalid template. Either filename should be provided: " + JSON.stringify(template)); + } + if (template.filename.endsWith(".d.ts")) { + template.write = true; + } + if (!template.dst) { + const nuxt = useNuxt(); + template.dst = resolve(nuxt.options.buildDir, template.filename); + } + return template; +} +function addWebpackPlugin(plugin, options) { + extendWebpackConfig((config) => { + config.plugins = config.plugins || []; + if (Array.isArray(plugin)) { + config.plugins.push(...plugin); + } else { + config.plugins.push(plugin); + } + }, options); +} +function extendWebpackConfig(fn, options = {}) { + const nuxt = useNuxt(); + if (options.dev === false && nuxt.options.dev) { + return; + } + if (options.build === false && nuxt.options.build) { + return; + } + nuxt.hook("webpack:config", (configs) => { + if (options.server !== false) { + const config = configs.find((i) => i.name === "server"); + if (config) { + fn(config); + } + } + if (options.client !== false) { + const config = configs.find((i) => i.name === "client"); + if (config) { + fn(config); + } + } + }); +} + +const boolToText = (value) => value ? "enabled" : "disabled"; +const envToBool = (env) => Boolean(env && env.toLowerCase() !== "false" && env !== "0"); +const canInitialize = (options) => Boolean(options.initialize && options.dsn); +const clientSentryEnabled = (options) => !options.disabled && !options.disableClientSide; +const serverSentryEnabled = (options) => !options.disabled && !options.disableServerSide; +function callOnce(fn) { + let called = false; + return function callOnceWrapper(...subargs) { + if (!called) { + called = true; + return fn(...subargs); + } + }; +} + +const jiti = initJiti(fileURLToPath(import.meta.url)); +const BROWSER_CORE_INTEGRATIONS = { + FunctionToString: true, + InboundFilters: true, + LinkedErrors: true +}; +const BROWSER_INTEGRATIONS = { + Breadcrumbs: true, + GlobalHandlers: true, + HttpContext: true, + Replay: true, + TryCatch: true, + // Integrations that are now exported from @sentry/browser + CaptureConsole: true, + ContextLines: true, + Debug: true, + Dedupe: true, + ExtraErrorData: true, + HttpClient: true, + ReportingObserver: true, + RewriteFrames: true, + SessionTiming: true +}; +const SERVER_CORE_INTEGRATIONS = { + FunctionToString: true, + InboundFilters: true, + LinkedErrors: true, + RequestData: true +}; +const SERVER_NODE_INTEGRATIONS = { + Anr: true, + Console: true, + Context: true, + ContextLines: true, + Express: true, + GraphQL: true, + Hapi: true, + Http: true, + LocalVariables: true, + Modules: true, + Mongo: true, + Mysql: true, + OnUncaughtException: true, + OnUnhandledRejection: true, + Postgres: true, + Prisma: true, + Spotlight: true, + Undici: true, + // Integrations that are now exported from @sentry/node and @sentry/browser: + CaptureConsole: true, + Debug: true, + Dedupe: true, + ExtraErrorData: true, + HttpClient: true, + ReportingObserver: true, + RewriteFrames: true, + SessionTiming: true +}; +const INTEGRATION_TO_IMPORT_NAME_MAP = { + Anr: "anrIntegration", + Breadcrumbs: "breadcrumbsIntegration", + CaptureConsole: "captureConsoleIntegration", + Console: "consoleIntegration", + Context: "nodeContextIntegration", + ContextLines: "contextLinesIntegration", + Debug: "debugIntegration", + Dedupe: "dedupeIntegration", + Express: "expressIntegration", + ExtraErrorData: "extraErrorDataIntegration", + FunctionToString: "functionToStringIntegration", + GlobalHandlers: "globalHandlersIntegration", + GraphQL: "graphqlIntegration", + Hapi: "hapiIntegration", + Http: "httpIntegration", + HttpClient: "httpClientIntegration", + HttpContext: "httpContextIntegration", + InboundFilters: "inboundFiltersIntegration", + LinkedErrors: "linkedErrorsIntegration", + LocalVariables: "localVariablesIntegration", + Modules: "modulesIntegration", + Mongo: "mongoIntegration", + Mysql: "mysqlIntegration", + OnUncaughtException: "onUncaughtExceptionIntegration", + OnUnhandledRejection: "onUnhandledRejectionIntegration", + Postgres: "postgresIntegration", + Prisma: "prismaIntegration", + ProfilingIntegration: "nodeProfilingIntegration", + Replay: "replayIntegration", + ReportingObserver: "reportingObserverIntegration", + RequestData: "requestDataIntegration", + RewriteFrames: "rewriteFramesIntegration", + SessionTiming: "sessionTimingIntegration", + Spotlight: "spotlightIntegration", + TryCatch: "browserApiErrorsIntegration", + Undici: "nativeNodeFetchIntegration" +}; +function mapIntegrationToImportName(key) { + return INTEGRATION_TO_IMPORT_NAME_MAP[key]; +} +const SERVER_PROFILING_INTEGRATION = "ProfilingIntegration"; +function getEnabledIntegrations(integrations) { + return getIntegrationsKeys(integrations).filter((key) => integrations[key]); +} +function getDisabledIntegrationKeys(integrations) { + return getIntegrationsKeys(integrations).filter((key) => integrations[key] === false); +} +function getIntegrationsKeys(integrations) { + return Object.keys(integrations); +} +function isBrowserCoreIntegration(name) { + return name in BROWSER_CORE_INTEGRATIONS; +} +function isBrowserDefaultIntegration(name) { + return name in BROWSER_INTEGRATIONS; +} +function isServerCoreIntegration(name) { + return name in SERVER_CORE_INTEGRATIONS; +} +function isServerNodeIntegration(name) { + return name in SERVER_NODE_INTEGRATIONS; +} +async function getApiMethods(packageName) { + const packageApi = await import(packageName); + const apiMethods = []; + for (const key in packageApi) { + if (key === "default") { + for (const subKey in packageApi[key]) { + if (typeof packageApi[key][subKey] === "function") { + apiMethods.push(subKey); + } + } + continue; + } + if (typeof packageApi[key] === "function") { + apiMethods.push(key); + } + } + return apiMethods; +} +async function resolveRelease(moduleOptions) { + if (!("release" in moduleOptions.config)) { + try { + const SentryCli = await import('@sentry/cli').then((m) => m.default || m); + const cli = new SentryCli(); + return (await cli.releases.proposeVersion()).trim(); + } catch { + } + } +} +function resolveClientLazyOptions(options, apiMethods, logger) { + if (!options.lazy) { + return; + } + const defaultLazyOptions = { + injectMock: true, + injectLoadHook: false, + mockApiMethods: true, + chunkName: "sentry", + webpackPrefetch: false, + webpackPreload: false + }; + options.lazy = defu(options.lazy, defaultLazyOptions); + if (!options.lazy.injectMock) { + options.lazy.mockApiMethods = []; + } else if (options.lazy.mockApiMethods === true) { + options.lazy.mockApiMethods = apiMethods; + } else if (Array.isArray(options.lazy.mockApiMethods)) { + const mockMethods = options.lazy.mockApiMethods; + options.lazy.mockApiMethods = mockMethods.filter((method) => apiMethods.includes(method)); + const notfoundMethods = mockMethods.filter((method) => !apiMethods.includes(method)); + if (notfoundMethods.length) { + logger.warn("Some specified methods to mock weren't found in @sentry/vue:", notfoundMethods); + } + if (!options.lazy.mockApiMethods.includes("captureException")) { + options.lazy.mockApiMethods.push("captureException"); + } + } +} +function resolveTracingOptions(options) { + if (!options.tracing) { + return; + } + const defaultTracingOptions = { + tracesSampleRate: 1, + browserTracing: { + routeLabel: "name" + }, + vueOptions: { + trackComponents: true + } + }; + options.tracing = defu(options.tracing, defaultTracingOptions); + if (options.config.tracesSampleRate === void 0) { + options.config.tracesSampleRate = options.tracing.tracesSampleRate; + } +} +async function resolveClientOptions(nuxt, moduleOptions, logger) { + const options = defu(moduleOptions); + let clientConfigPath; + if (typeof options.clientConfig === "string") { + clientConfigPath = resolveAlias(options.clientConfig); + clientConfigPath = relative(nuxt.options.buildDir, clientConfigPath); + } else { + options.config = defu(options.clientConfig, options.config); + } + const apiMethods = await getApiMethods("@sentry/vue"); + resolveClientLazyOptions(options, apiMethods, logger); + resolveTracingOptions(options); + for (const name of getIntegrationsKeys(options.clientIntegrations)) { + if (!isBrowserDefaultIntegration(name) && !isBrowserCoreIntegration(name)) { + logger.warn(`Sentry clientIntegration "${name}" is not recognized and will be ignored.`); + delete options.clientIntegrations[name]; + } + } + let customClientIntegrations; + if (options.customClientIntegrations) { + if (typeof options.customClientIntegrations === "string") { + customClientIntegrations = resolveAlias(options.customClientIntegrations); + customClientIntegrations = relative(nuxt.options.buildDir, customClientIntegrations); + } else { + logger.warn(`Invalid customClientIntegrations option. Expected a file path, got "${typeof options.customClientIntegrations}".`); + } + } + const importsBrowser = []; + const importsCore = []; + const integrations = getEnabledIntegrations(options.clientIntegrations).reduce((res, key) => { + const importName = mapIntegrationToImportName(key); + if (key in BROWSER_INTEGRATIONS) { + importsBrowser.push(importName); + } else if (key in BROWSER_CORE_INTEGRATIONS) { + importsCore.push(importName); + } + res[importName] = options.clientIntegrations[key]; + return res; + }, {}); + const imports = { + "~@sentry/browser": importsBrowser, + "~@sentry/core": importsCore, + "~@sentry/vue": ["init", ...options.tracing ? ["browserTracingIntegration"] : []] + }; + return { + dev: nuxt.options.dev, + runtimeConfigKey: options.runtimeConfigKey, + config: { + dsn: options.dsn, + ...options.config + }, + clientConfigPath, + DISABLED_INTEGRATION_KEYS: getDisabledIntegrationKeys(options.clientIntegrations), + lazy: options.lazy, + apiMethods, + customClientIntegrations, + logMockCalls: options.logMockCalls, + // for mocked only + tracing: options.tracing, + imports, + initialize: canInitialize(options), + integrations + }; +} +async function resolveServerOptions(nuxt, moduleOptions, logger) { + const options = defu(moduleOptions); + if (options.tracing) { + resolveTracingOptions(options); + options.serverIntegrations = defu(options.serverIntegrations, { Http: {} }); + } + if (typeof options.serverConfig === "string") { + const resolvedPath = resolveAlias(options.serverConfig); + try { + const mod = jiti(resolvedPath); + options.serverConfig = (mod.default || mod)(); + } catch (error) { + logger.error(`Error handling the serverConfig plugin: +${error}`); + } + } + options.config = defu(getServerRuntimeConfig(nuxt, options), options.serverConfig, options.config); + for (const name of getIntegrationsKeys(options.serverIntegrations)) { + if (!isServerNodeIntegration(name) && !isServerCoreIntegration(name) && name !== SERVER_PROFILING_INTEGRATION) { + logger.warn(`Sentry serverIntegration "${name}" is not recognized and will be ignored.`); + delete options.serverIntegrations[name]; + } + } + let customIntegrations = []; + if (options.customServerIntegrations) { + const resolvedPath = resolveAlias(options.customServerIntegrations); + try { + const mod = jiti(resolvedPath); + customIntegrations = (mod.default || mod)(); + if (!Array.isArray(customIntegrations)) { + logger.error(`Invalid value returned from customServerIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`); + } + } catch (error) { + logger.error(`Error handling the customServerIntegrations plugin: +${error}`); + } + } + if (SERVER_PROFILING_INTEGRATION in options.serverIntegrations) { + const enabled = options.serverIntegrations[SERVER_PROFILING_INTEGRATION]; + delete options.serverIntegrations[SERVER_PROFILING_INTEGRATION]; + if (enabled) { + try { + const { nodeProfilingIntegration } = await import('@sentry/profiling-node').then((m) => m.default || m); + customIntegrations.push(nodeProfilingIntegration()); + } catch (error) { + logger.error(`To use the ${SERVER_PROFILING_INTEGRATION} integration you need to install the "@sentry/profiling-node" dependency.`); + throw new Error(error.message); + } + } + } + const resolvedIntegrations = [ + // Automatically instrument Node.js libraries and frameworks + ...getEnabledIntegrations(options.serverIntegrations).map((name) => { + const importName = mapIntegrationToImportName(name); + const opt = options.serverIntegrations[name]; + try { + if (isServerCoreIntegration(name)) { + return Object.keys(opt).length ? SentryCore[importName](opt) : SentryCore[importName](); + } else if (isServerNodeIntegration(name)) { + return Object.keys(opt).length ? Sentry[importName](opt) : Sentry[importName](); + } else { + throw new Error(`Unsupported server integration "${name}"`); + } + } catch (error) { + throw new Error(`Failed initializing server integration "${name}". +${error}`); + } + }), + ...customIntegrations + ]; + const disabledIntegrationKeys = getDisabledIntegrationKeys(options.serverIntegrations); + options.config.integrations = (defaultIntegrations) => { + return [ + ...defaultIntegrations.filter((integration) => !disabledIntegrationKeys.includes(integration.name)), + ...resolvedIntegrations + ]; + }; + return { + config: { + dsn: options.dsn, + ...options.config + }, + apiMethods: await getApiMethods("@sentry/node"), + lazy: options.lazy, + logMockCalls: options.logMockCalls, + // for mocked only + tracing: options.tracing + }; +} +function getServerRuntimeConfig(nuxt, options) { + const { publicRuntimeConfig } = nuxt.options; + const { runtimeConfigKey } = options; + if (publicRuntimeConfig && typeof publicRuntimeConfig !== "function" && runtimeConfigKey in publicRuntimeConfig) { + return defu( + publicRuntimeConfig[runtimeConfigKey].serverConfig, + publicRuntimeConfig[runtimeConfigKey].config + ); + } +} + +const RESOLVED_RELEASE_FILENAME = "sentry.release.config.mjs"; +async function buildHook(nuxt, moduleOptions, logger) { + const release = await resolveRelease(moduleOptions); + const templateDir = fileURLToPath$1(new URL("./templates", import.meta.url)); + const pluginOptionClient = clientSentryEnabled(moduleOptions) && canInitialize(moduleOptions) ? moduleOptions.lazy ? "lazy" : "client" : "mocked"; + const clientOptions = defu({ config: { release } }, await resolveClientOptions(nuxt, moduleOptions, logger)); + addPluginTemplate({ + src: resolve$1(templateDir, `plugin.${pluginOptionClient}.js`), + filename: "sentry.client.js", + mode: "client", + options: clientOptions + }); + if (pluginOptionClient !== "mocked") { + addTemplate({ + src: resolve$1(templateDir, "client.shared.js"), + filename: "sentry.client.shared.js", + options: clientOptions + }); + } + const pluginOptionServer = serverSentryEnabled(moduleOptions) ? "server" : "mocked"; + const serverOptions = defu({ config: { release } }, await resolveServerOptions(nuxt, moduleOptions, logger)); + addPluginTemplate({ + src: resolve$1(templateDir, `plugin.${pluginOptionServer}.js`), + filename: "sentry.server.js", + mode: "server", + options: serverOptions + }); + if (serverSentryEnabled(moduleOptions)) { + addTemplate({ + src: resolve$1(templateDir, "options.ejs"), + filename: RESOLVED_RELEASE_FILENAME, + options: { release } + }); + } + if (!clientOptions.dev && !clientOptions.config.debug) { + const webpack = await import('webpack').then((m) => m.default || m); + addWebpackPlugin(new webpack.DefinePlugin({ + __SENTRY_DEBUG__: "false" + })); + } +} +async function webpackConfigHook(nuxt, webpackConfigs, options, logger) { + let WebpackPlugin; + try { + WebpackPlugin = await import('@sentry/webpack-plugin').then((m) => m.default || m); + } catch { + throw new Error('The "@sentry/webpack-plugin" package must be installed as a dev dependency to use the "publishRelease" option.'); + } + const publishRelease = defu(options.publishRelease); + if (!publishRelease.sourcemaps) { + publishRelease.sourcemaps = {}; + } + if (!publishRelease.sourcemaps.ignore) { + publishRelease.sourcemaps.ignore = []; + } + if (!Array.isArray(publishRelease.sourcemaps.ignore)) { + publishRelease.sourcemaps.ignore = [publishRelease.sourcemaps.ignore]; + } + if (!publishRelease.release) { + publishRelease.release = {}; + } + publishRelease.release.name = publishRelease.release.name || options.config.release || await resolveRelease(options); + if (!publishRelease.release.name) { + logger.warn('Sentry release will not be published because "config.release" or "publishRelease.release.name" was not set nor it was possible to determine it automatically from the repository.'); + return; + } + for (const config of webpackConfigs) { + config.devtool = options.sourceMapStyle; + config.plugins = config.plugins || []; + config.plugins.push(WebpackPlugin.sentryWebpackPlugin(publishRelease)); + } +} +async function initializeServerSentry(nuxt, moduleOptions, logger) { + if (process.sentry) { + return; + } + let release; + try { + const path = resolve$1(nuxt.options.buildDir, RESOLVED_RELEASE_FILENAME); + release = (await import(path)).release; + } catch { + } + const serverOptions = await resolveServerOptions(nuxt, moduleOptions, logger); + const config = defu({ release }, serverOptions.config); + process.sentry = Sentry; + if (canInitialize(moduleOptions)) { + Sentry.init(config); + nuxt.hook("render:setupMiddleware", (app) => app.use(() => Sentry.setupExpressErrorHandler(app))); + } +} +async function shutdownServerSentry() { + if (process.sentry) { + await process.sentry.close(); + process.sentry = void 0; + } +} + +const logger = useLogger("nuxt:sentry"); +const moduleDir = fileURLToPath(new URL("./", import.meta.url)); +const module = defineNuxtModule({ + meta: { + name: "@nuxtjs/sentry", + configKey: "sentry" + }, + defaults: (nuxt) => ({ + lazy: false, + dsn: process.env.SENTRY_DSN || "", + disabled: envToBool(process.env.SENTRY_DISABLED) || false, + initialize: envToBool(process.env.SENTRY_INITIALIZE) || true, + runtimeConfigKey: "sentry", + disableClientSide: envToBool(process.env.SENTRY_DISABLE_CLIENT_SIDE) || false, + disableServerSide: envToBool(process.env.SENTRY_DISABLE_SERVER_SIDE) || false, + publishRelease: envToBool(process.env.SENTRY_PUBLISH_RELEASE) || false, + disableServerRelease: envToBool(process.env.SENTRY_DISABLE_SERVER_RELEASE) || false, + disableClientRelease: envToBool(process.env.SENTRY_DISABLE_CLIENT_RELEASE) || false, + logMockCalls: true, + sourceMapStyle: "hidden-source-map", + tracing: false, + clientIntegrations: { + ExtraErrorData: {}, + ReportingObserver: { types: ["crash"] } + }, + serverIntegrations: { + Dedupe: {}, + ExtraErrorData: {}, + RewriteFrames: { root: nuxt.options.rootDir } + }, + customClientIntegrations: "", + customServerIntegrations: "", + config: { + environment: nuxt.options.dev ? "development" : "production" + }, + serverConfig: {}, + clientConfig: {} + }), + async setup(options, nuxt) { + const defaultsPublishRelease = { + sourcemaps: { + ignore: [ + "node_modules/**/*" + ] + } + }; + if (options.publishRelease) { + options.publishRelease = defu(options.publishRelease, defaultsPublishRelease); + } + if (canInitialize(options) && (clientSentryEnabled(options) || serverSentryEnabled(options))) { + const status = `(client side: ${boolToText(clientSentryEnabled(options))}, server side: ${boolToText(serverSentryEnabled(options))})`; + logger.success(`Sentry reporting is enabled ${status}`); + } else { + let why; + if (options.disabled) { + why = '"disabled" option has been set'; + } else if (!options.dsn) { + why = "no DSN has been provided"; + } else if (!options.initialize) { + why = '"initialize" option has been set to false'; + } else { + why = "both client and server side clients are disabled"; + } + logger.info(`Sentry reporting is disabled (${why})`); + } + const aliasedDependencies = [ + "lodash.mergewith", + "@sentry/browser", + "@sentry/core", + "@sentry/utils", + "@sentry/vue" + ]; + for (const dep of aliasedDependencies) { + nuxt.options.alias[`~${dep}`] = (await resolvePath(dep, { url: moduleDir })).replace(/\/cjs\//, "/esm/"); + } + if (serverSentryEnabled(options)) { + nuxt.hook("generate:routeFailed", ({ route, errors }) => { + errors.forEach(({ error }) => withScope((scope) => { + scope.setExtra("route", route); + captureException(error); + })); + }); + { + const isBuilding = nuxt.options._build && !nuxt.options.dev; + const initHook = isBuilding ? "build:compile" : "ready"; + nuxt.hook(initHook, () => initializeServerSentry(nuxt, options, logger)); + const shutdownHook = isBuilding ? "build:done" : "close"; + const shutdownServerSentryOnce = callOnce(() => shutdownServerSentry()); + nuxt.hook(shutdownHook, shutdownServerSentryOnce); + } + } + nuxt.hook("build:before", () => buildHook(nuxt, options, logger)); + if (options.publishRelease && !options.disabled && !nuxt.options.dev) { + { + nuxt.hook("webpack:config", (webpackConfigs) => webpackConfigHook(nuxt, webpackConfigs, options, logger)); + } + } + } +}); + +export { module as default }; diff --git a/dist/templates/client.shared.js b/dist/templates/client.shared.js new file mode 100644 index 00000000..25332f05 --- /dev/null +++ b/dist/templates/client.shared.js @@ -0,0 +1,92 @@ +import merge from '~lodash.mergewith' +import * as CoreSdk from '~@sentry/core' +import { captureUserFeedback, forceLoad, onLoad, showReportDialog } from '~@sentry/browser' +<% +for (const [package, imports] of Object.entries(options.imports)) { + if (imports.length) { + %>import { <%= imports.join(', ') %> } from '<%= package %>' +<% + } +} +if (options.clientConfigPath) {%>import getClientConfig from '<%= options.clientConfigPath %>' +<%} +if (options.customClientIntegrations) {%>import getCustomIntegrations from '<%= options.customClientIntegrations %>' +<%}%> + +export { init } +export const SentrySdk = { ...CoreSdk, ...{ captureUserFeedback, forceLoad, onLoad, showReportDialog } } + +/** @type {string[]} */ +const DISABLED_INTEGRATION_KEYS = <%= serialize(options.DISABLED_INTEGRATION_KEYS) %> + +/** + * @typedef {Parameters[0]} InitConfig + * @param {import('@nuxt/types').Context} ctx + * @return {Promise} + */ +export<%= (options.clientConfigPath || options.customClientIntegrations) ? ' async' : '' %> function getConfig (ctx) { + /** @type {InitConfig} */ + const config = { + <%= Object + .entries(options.config) + .map(([key, option]) => { + const value = typeof option === 'function' ? serializeFunction(option) : serialize(option) + return `${key}:${value}` + }) + .join(',\n ') %>, + } + + /** @type {NonNullable['integrations']} */ + const resolvedIntegrations = [ + <%= Object + .entries(options.integrations) + .filter(([name]) => name !== 'Vue') + .map(([name, integration]) => { + const integrationOptions = Object + .entries(integration) + .map(([key, option]) => { + const value = typeof option === 'function' ? serializeFunction(option) : serialize(option) + return `${key}:${value}` + }) + + return `${name}(${integrationOptions.length ? '{ ' + integrationOptions.join(',') + ' }' : ''})` + }) + .join(',\n ') %>, + ] + <% + if (options.tracing) { + const { browserTracing, vueOptions, ...tracingOptions } = options.tracing + %> + resolvedIntegrations.push(browserTracingIntegration({ + router: ctx.app.router, + ...<%= serialize(browserTracing) %>, + })) + merge(config, <%= serialize(vueOptions) %>, <%= serialize(tracingOptions) %>) + <% } %> + + <% if (options.clientConfigPath) { %> + const clientConfig = await getClientConfig(ctx) + clientConfig ? merge(config, clientConfig) : console.error(`[@nuxtjs/sentry] Invalid value returned from the clientConfig plugin.`) + <% } %> + + <% if (options.customClientIntegrations) { %> + const customIntegrations = await getCustomIntegrations(ctx) + if (Array.isArray(customIntegrations)) { + resolvedIntegrations.push(...customIntegrations) + } else { + console.error(`[@nuxtjs/sentry] Invalid value returned from customClientIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`) + } + <% } %> + config.integrations = (defaultIntegrations) => { + return [ + ...defaultIntegrations.filter(integration => !DISABLED_INTEGRATION_KEYS.includes(integration.name)), + ...resolvedIntegrations, + ] + } + const runtimeConfigKey = <%= serialize(options.runtimeConfigKey) %> + if (ctx.$config && runtimeConfigKey && ctx.$config[runtimeConfigKey]) { + merge(config, ctx.$config[runtimeConfigKey].config, ctx.$config[runtimeConfigKey].clientConfig) + } + + return config +} diff --git a/dist/templates/options.ejs b/dist/templates/options.ejs new file mode 100644 index 00000000..eaec7491 --- /dev/null +++ b/dist/templates/options.ejs @@ -0,0 +1,7 @@ +/* eslint-disable */ +<% +for (const [key, value] of Object.entries(options)) { +%>export const <%= key %> = <%= serialize(value) %> +<% +} +%> diff --git a/dist/templates/plugin.client.js b/dist/templates/plugin.client.js new file mode 100644 index 00000000..cd5e35ff --- /dev/null +++ b/dist/templates/plugin.client.js @@ -0,0 +1,10 @@ +import Vue from 'vue' +import { getConfig, init, SentrySdk } from './sentry.client.shared' + +/** @type {import('@nuxt/types').Plugin} */ +export default async function (ctx, inject) { + const config = await getConfig(ctx) + init({ Vue, ...config }) + inject('sentry', SentrySdk) + ctx.$sentry = SentrySdk +} diff --git a/dist/templates/plugin.lazy.js b/dist/templates/plugin.lazy.js new file mode 100644 index 00000000..b2ba37b2 --- /dev/null +++ b/dist/templates/plugin.lazy.js @@ -0,0 +1,172 @@ +import Vue from 'vue' + +<% if (options.lazy.injectMock) { %> +const API_METHODS = <%= JSON.stringify(options.lazy.mockApiMethods)%> +let delayedCalls = [] +let SentryMock = {} +<% } %> +let sentryReadyResolve +let loadInitiated = false +let loadCompleted = false + +<% if (options.lazy.injectMock) { %> +let delayedGlobalErrors = [] +let delayedUnhandledRejections = [] +/** @param {ErrorEvent} event */ +const delayGlobalError = function (event) { + delayedGlobalErrors.push([event.message, event.filename, event.lineno, event.colno, event.error]) +} +const delayUnhandledRejection = function (event) { + if ('reason' in event && event.reason) { + event = event.reason + } else if ('detail' in event && event.detail && 'reason' in event.detail && event.detail.reason) { + event = event.detail.reason + } + delayedUnhandledRejections.push(event) +} + +const vueErrorHandler = Vue.config.errorHandler + +Vue.config.errorHandler = (error, vm, info) => { + if (!loadCompleted) { + if (vm) { + vm.$sentry.captureException(error) + } + + if (Vue.util) { + Vue.util.warn(`Error in ${info}: "${error.toString()}"`, vm) + } + console.error(error) + } + + if (vueErrorHandler) { + return vueErrorHandler(error, vm, info) + } +} +<% } %> + +export default function SentryPlugin (ctx, inject) { + <% if (options.lazy.injectMock) { %> + API_METHODS.forEach((key) => { + SentryMock[key] = (...args) => delayedCalls.push([key, args]) + }) + + window.addEventListener('error', delayGlobalError) + window.addEventListener('unhandledrejection', delayUnhandledRejection) + + inject('sentry', SentryMock) + ctx.$sentry = SentryMock + <% } %> + + const loadSentryHook = () => attemptLoadSentry(ctx, inject) + + <% if (options.lazy.injectLoadHook) { %> + inject('sentryLoad', loadSentryHook) + ctx.$sentryLoad = loadSentryHook + <% } else { %> + window.<%= globals.readyCallback %>(loadSentryHook) + <% } %> + + const sentryReadyPromise = new Promise((resolve) => { + sentryReadyResolve = resolve + }) + + const sentryReady = () => sentryReadyPromise + + inject('sentryReady', sentryReady) + ctx.$sentryReady = sentryReady +} + +async function attemptLoadSentry (ctx, inject) { + if (loadInitiated) { + return + } + + loadInitiated = true + + if (!window.<%= globals.nuxt %>) { + <% if (options.dev) { %> + console.warn('$sentryLoad was called but window.<%= globals.nuxt %> is not available, delaying sentry loading until onNuxtReady callback. Do you really need to use lazy loading for Sentry?') + <% } + if (options.lazy.injectLoadHook) { %>window.<%= globals.readyCallback %>(() => loadSentry(ctx, inject)) + <% } else { %>// Wait for onNuxtReady hook to trigger. + <% } %>return + } + + await loadSentry(ctx, inject) +} + +async function loadSentry (ctx, inject) { + if (loadCompleted) { + return + } + + <% + const magicComments = [`webpackChunkName: '${options.lazy.chunkName}'`] + if (options.lazy.webpackPrefetch) { + magicComments.push('webpackPrefetch: true') + } + if (options.lazy.webpackPreload) { + magicComments.push('webpackPreload: true') + } + %> + const { getConfig, init, SentrySdk } = await import(/* <%= magicComments.join(', ') %> */ './sentry.client.shared') + <% if (options.initialize) {%> + const config = await getConfig(ctx) + init({ Vue, ...config }) + <% } %> + + loadCompleted = true + <% if (options.lazy.injectMock) { %> + window.removeEventListener('error', delayGlobalError) + window.removeEventListener('unhandledrejection', delayUnhandledRejection) + if (delayedGlobalErrors.length) { + if (window.onerror) { + console.info('Reposting global errors after Sentry has loaded') + for (const errorArgs of delayedGlobalErrors) { + window.onerror.apply(window, errorArgs) + } + } + delayedGlobalErrors = [] + } + if (delayedUnhandledRejections.length) { + if (window.onunhandledrejection) { + console.info('Reposting unhandled promise rejection errors after Sentry has loaded') + for (const reason of delayedUnhandledRejections) { + window.onunhandledrejection(reason) + } + } + delayedUnhandledRejections = [] + } + delayedCalls.forEach(([methodName, args]) => SentrySdk[methodName].apply(SentrySdk, args)) + <% } %> + forceInject(ctx, inject, 'sentry', SentrySdk) + sentryReadyResolve(SentrySdk) + + // help gc + <% if (options.lazy.injectMock) { %> + // Avoid crashes in case the reference to the mocked object is being used after the actual Sentry instance has loaded. + API_METHODS.forEach((key) => { + SentryMock[key] = (...args) => SentrySdk[key].apply(SentrySdk, args) + }) + + // Dont unset delayedCalls & SentryMock during development - this will cause HMR issues. + <% if (!options.dev) { %> + delayedCalls = undefined + SentryMock = undefined + <% } else { %> + delayedCalls = [] + <% } %> + <% } %> + sentryReadyResolve = undefined +} + +// Custom inject function that is able to overwrite previously injected values, +// which original inject doesn't allow to do. +// This method is adapted from the inject method in nuxt/vue-app/template/index.js +function forceInject (ctx, inject, key, value) { + inject(key, value) + const injectKey = '$' + key + ctx[injectKey] = value + window.<%= globals.nuxt %>.$options[injectKey] = value +} diff --git a/dist/templates/plugin.mocked.js b/dist/templates/plugin.mocked.js new file mode 100644 index 00000000..af27ebe6 --- /dev/null +++ b/dist/templates/plugin.mocked.js @@ -0,0 +1,15 @@ +const apiMethods = <%= JSON.stringify(options.apiMethods)%> + +/** @type {import('@nuxt/types').Plugin} */ +export default function (ctx, inject) { + const SentryMock = {} + apiMethods.forEach(key => { + SentryMock[key] = <%= options.logMockCalls + ? '(...args) => console.warn(`$sentry.${key}() called, but Sentry plugin is disabled. Arguments:`, args)' + : '_ => _'%> + }) + + // Inject mocked sentry to the context as $sentry (this is used in case sentry is disabled) + inject('sentry', SentryMock) + ctx.$sentry = SentryMock +} diff --git a/dist/templates/plugin.server.js b/dist/templates/plugin.server.js new file mode 100644 index 00000000..7e68523b --- /dev/null +++ b/dist/templates/plugin.server.js @@ -0,0 +1,46 @@ +<% if (options.tracing) { %> +import { getActiveSpan, getDynamicSamplingContextFromSpan, spanToTraceHeader } from '~@sentry/core' +import { dynamicSamplingContextToSentryBaggageHeader } from '~@sentry/utils' +<% } %> + +/** @type {import('@nuxt/types').Plugin} */ +export default function (ctx, inject) { + const sentry = process.sentry || null + if (!sentry) { + return + } + inject('sentry', sentry) + ctx.$sentry = sentry + <% if (options.tracing) { %> + connectBackendTraces(ctx) + <% } %> + <% if (options.lazy) { %> + const sentryReady = () => Promise.resolve(sentry) + inject('sentryReady', sentryReady) + ctx.$sentryReady = sentryReady + <% } %> +} + +<% if (options.tracing) { %> +/** @param {import('@nuxt/types').Context} ctx */ +function connectBackendTraces (ctx) { + const { head } = ctx.app + if (!head || head instanceof Function) { + console.warn('[@nuxtjs/sentry] can not connect backend and frontend traces because app.head is a function or missing!') + return + } + const span = getActiveSpan() + if (!span) { + return + } + head.meta = head.meta || [] + head.meta.push({ hid: 'sentry-trace', name: 'sentry-trace', content: spanToTraceHeader(span) }) + const dsc = getDynamicSamplingContextFromSpan(span) + if (dsc) { + const baggage = dynamicSamplingContextToSentryBaggageHeader(dsc) + if (baggage) { + head.meta.push({ hid: 'sentry-baggage', name: 'baggage', content: baggage }) + } + } +} +<% } %> diff --git a/dist/types.d.mts b/dist/types.d.mts new file mode 100644 index 00000000..7dd39c96 --- /dev/null +++ b/dist/types.d.mts @@ -0,0 +1,18 @@ + +import type { ModuleOptions, ModulePublicRuntimeConfig } from './module.js' + + +declare module '@nuxt/schema' { + interface NuxtConfig { ['sentry']?: Partial } + interface NuxtOptions { ['sentry']?: ModuleOptions } + interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {} +} + +declare module 'nuxt/schema' { + interface NuxtConfig { ['sentry']?: Partial } + interface NuxtOptions { ['sentry']?: ModuleOptions } + interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {} +} + + +export type { ModuleOptions, ModulePublicRuntimeConfig, default } from './module.js' diff --git a/dist/types.d.ts b/dist/types.d.ts new file mode 100644 index 00000000..9ea9c960 --- /dev/null +++ b/dist/types.d.ts @@ -0,0 +1,18 @@ + +import type { ModuleOptions, ModulePublicRuntimeConfig } from './module' + + +declare module '@nuxt/schema' { + interface NuxtConfig { ['sentry']?: Partial } + interface NuxtOptions { ['sentry']?: ModuleOptions } + interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {} +} + +declare module 'nuxt/schema' { + interface NuxtConfig { ['sentry']?: Partial } + interface NuxtOptions { ['sentry']?: ModuleOptions } + interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {} +} + + +export type { ModuleOptions, ModulePublicRuntimeConfig, default } from './module'