@nuxtjs/sentry
Version:
Sentry module for Nuxt.js
820 lines (813 loc) • 29.1 kB
JavaScript
import { fileURLToPath } from 'node:url';
import { defu } from 'defu';
import { resolvePath } from 'mlly';
import * as Sentry from '@sentry/node';
import { autoDiscoverNodePerformanceMonitoringIntegrations, Integrations, 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';
import * as PluggableIntegrations from '@sentry/integrations';
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
};
const BROWSER_PLUGGABLE_INTEGRATIONS = {
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,
Apollo: 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
};
const SERVER_PLUGGABLE_INTEGRATIONS = {
CaptureConsole: true,
Debug: true,
Dedupe: true,
ExtraErrorData: true,
HttpClient: true,
ReportingObserver: true,
RewriteFrames: true,
SessionTiming: true
};
const INTEGRATION_TO_IMPORT_NAME_MAP = {
Anr: "Anr",
Apollo: "Apollo",
Breadcrumbs: "breadcrumbsIntegration",
CaptureConsole: "captureConsoleIntegration",
Console: "Console",
Context: "Context",
ContextLines: "contextLinesIntegration",
Debug: "debugIntegration",
Dedupe: "dedupeIntegration",
Express: "Express",
ExtraErrorData: "extraErrorDataIntegration",
FunctionToString: "functionToStringIntegration",
GlobalHandlers: "globalHandlersIntegration",
GraphQL: "GraphQL",
Hapi: "Hapi",
Http: "Http",
HttpClient: "httpClientIntegration",
HttpContext: "httpContextIntegration",
InboundFilters: "inboundFiltersIntegration",
LinkedErrors: "linkedErrorsIntegration",
LocalVariables: "LocalVariables",
Modules: "Modules",
Mongo: "Mongo",
Mysql: "Mysql",
OnUncaughtException: "OnUncaughtException",
OnUnhandledRejection: "OnUnhandledRejection",
Postgres: "Postgres",
Prisma: "Prisma",
ProfilingIntegration: "ProfilingIntegration",
Replay: "replayIntegration",
ReportingObserver: "reportingObserverIntegration",
RequestData: "requestDataIntegration",
RewriteFrames: "rewriteFramesIntegration",
SessionTiming: "sessionTimingIntegration",
Spotlight: "Spotlight",
TryCatch: "browserApiErrorsIntegration",
Undici: "Undici"
};
function mapClientIntegrationToImportName(key) {
return INTEGRATION_TO_IMPORT_NAME_MAP[key];
}
function mapServerIntegrationToImportName(key) {
if (key === "ContextLines") {
return "ContextLines";
}
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 isBrowserPluggableIntegration(name) {
return name in BROWSER_PLUGGABLE_INTEGRATIONS;
}
function isServerCoreIntegration(name) {
return name in SERVER_CORE_INTEGRATIONS;
}
function isServerNodeIntegration(name) {
return name in SERVER_NODE_INTEGRATIONS;
}
function isServerPlugabbleIntegration(name) {
return name in SERVER_PLUGGABLE_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: {},
vueOptions: {
trackComponents: true
},
vueRouterInstrumentationOptions: {
routeLabel: "name"
}
};
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) && !isBrowserPluggableIntegration(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 importsPluggable = [];
const integrations = getEnabledIntegrations(options.clientIntegrations).reduce((res, key) => {
const importName = mapClientIntegrationToImportName(key);
if (key in BROWSER_INTEGRATIONS) {
importsBrowser.push(importName);
} else if (key in BROWSER_CORE_INTEGRATIONS) {
importsCore.push(importName);
} else if (key in BROWSER_PLUGGABLE_INTEGRATIONS) {
importsPluggable.push(importName);
}
res[importName] = options.clientIntegrations[key];
return res;
}, {});
const imports = {
"~@sentry/browser": importsBrowser,
"~@sentry/core": importsCore,
"~@sentry/integrations": importsPluggable,
"~@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: { tracing: true } });
}
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) && !isServerPlugabbleIntegration(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 { ProfilingIntegration } = await import('@sentry/profiling-node').then((m) => m.default || m);
customIntegrations.push(new ProfilingIntegration());
} 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
...options.tracing ? autoDiscoverNodePerformanceMonitoringIntegrations() : [],
...getEnabledIntegrations(options.serverIntegrations).map((name) => {
const importName = mapServerIntegrationToImportName(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 ? new Integrations[name](opt) : new Integrations[name]();
} else if (isServerPlugabbleIntegration(name)) {
return Object.keys(opt).length ? PluggableIntegrations[importName](opt) : PluggableIntegrations[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, sentryHandlerProxy, 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);
sentryHandlerProxy.errorHandler = Sentry.Handlers.errorHandler();
sentryHandlerProxy.requestHandler = Sentry.Handlers.requestHandler(moduleOptions.requestHandlerConfig);
if (serverOptions.tracing) {
sentryHandlerProxy.tracingHandler = Sentry.Handlers.tracingHandler();
}
}
}
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: {},
requestHandlerConfig: {}
}),
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/integrations",
"@sentry/utils",
"@sentry/vue"
];
for (const dep of aliasedDependencies) {
nuxt.options.alias[`~${dep}`] = (await resolvePath(dep, { url: moduleDir })).replace(/\/cjs\//, "/esm/");
}
if (serverSentryEnabled(options)) {
const sentryHandlerProxy = {
errorHandler: (error, _, __, next) => {
next(error);
},
requestHandler: (_, __, next) => {
next();
},
tracingHandler: (_, __, next) => {
next();
}
};
nuxt.hook("render:setupMiddleware", (app) => app.use((req, res, next) => {
sentryHandlerProxy.requestHandler(req, res, next);
}));
if (options.tracing) {
nuxt.hook("render:setupMiddleware", (app) => app.use((req, res, next) => {
sentryHandlerProxy.tracingHandler(req, res, next);
}));
}
nuxt.hook("render:errorMiddleware", (app) => app.use((error, req, res, next) => {
sentryHandlerProxy.errorHandler(error, req, res, next);
}));
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, sentryHandlerProxy, 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 };