From c0076dd5c451341d388ee02335ecf997a420a45b Mon Sep 17 00:00:00 2001 From: DrJume <12785972+DrJume@users.noreply.github.com> Date: Wed, 23 Aug 2023 19:43:30 +0200 Subject: [PATCH] feat: first prototype --- src/module.ts | 93 +++++++++++++++++++++++++++++++----- src/runtime/app.config.ts | 19 ++++++++ src/runtime/plugin.ts | 5 -- src/runtime/sentry.client.ts | 28 +++++++++++ 4 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 src/runtime/app.config.ts delete mode 100644 src/runtime/plugin.ts create mode 100644 src/runtime/sentry.client.ts diff --git a/src/module.ts b/src/module.ts index 9d81ad5..6cd8104 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,19 +1,88 @@ -import { defineNuxtModule, addPlugin, createResolver } from "@nuxt/kit"; +import { type NuxtApp } from '#app/nuxt' +import { type SentryVitePluginOptions, sentryVitePlugin } from '@sentry/vite-plugin' +import { type Options as SentryVueOptions } from '@sentry/vue/types/types' +import defu from 'defu' +import { + addPlugin, + addVitePlugin, + createResolver, + defineNuxtModule, + resolvePath, + useLogger, +} from 'nuxt/kit' +import { type Plugin } from 'vite' -// Module options TypeScript interface definition -export interface ModuleOptions {} +export interface ModuleOptions { + /** @default true */ + deleteSourcemapsAfterUpload?: boolean + + vitePlugin: SentryVitePluginOptions +} + +declare module '@nuxt/schema' { + interface PublicRuntimeConfig { + sentry: + | undefined + | { + dsn?: string + } + } + interface NuxtConfig { + sentry?: ModuleOptions + } + interface AppConfigInput { + sentry?: { + client?: (app: NuxtApp) => Partial> + } + } +} + +const logger = useLogger('nuxt:sentry') export default defineNuxtModule({ meta: { - name: "@falcondev-it/nuxt-sentry", - configKey: "myModule", + name: '@falcondev-it/nuxt-sentry', + configKey: 'sentry', }, - // Default configuration options of the Nuxt module - defaults: {}, - setup(options, nuxt) { - const resolver = createResolver(import.meta.url); - // Do not add the extension since the `.ts` will be transpiled to `.mjs` after `npm run prepack` - addPlugin(resolver.resolve("./runtime/plugin")); + async setup(_moduleOptions, nuxt) { + if (!nuxt.options.runtimeConfig.public.sentry?.dsn) { + logger.warn('No Sentry DSN provided. Provide it in `runtimeConfig.public.sentry.dsn`') + return + } + + const moduleOptions = defu(_moduleOptions, { + deleteSourcemapsAfterUpload: true, + vitePlugin: { + disable: process.env.NODE_ENV !== 'production' || !process.env.CI, + release: { + deploy: { + env: process.env.NODE_ENV ?? 'development', + }, + }, + }, + } satisfies Partial) + + const resolver = createResolver(import.meta.url) + + const appConfigFile = await resolvePath(resolver.resolve('./runtime/app.config.ts')) + nuxt.hook('app:resolve', (app) => { + app.configs.push(appConfigFile) + }) + + nuxt.options.sourcemap.client = true + + const defaults: SentryVitePluginOptions = {} + if (moduleOptions.deleteSourcemapsAfterUpload) { + defaults.sourcemaps = { + filesToDeleteAfterUpload: ['.output/**/*.map'], + } + } + + nuxt.options.build.transpile.push('@sentry/vue') + + addVitePlugin(() => sentryVitePlugin(defu(moduleOptions.vitePlugin, defaults)) as Plugin) + + addPlugin(resolver.resolve('./runtime/sentry.client.ts')) }, -}); +}) diff --git a/src/runtime/app.config.ts b/src/runtime/app.config.ts new file mode 100644 index 0000000..3bd98ed --- /dev/null +++ b/src/runtime/app.config.ts @@ -0,0 +1,19 @@ +import { BrowserTracing, Replay, vueRouterInstrumentation } from '@sentry/vue' + +export default defineAppConfig({ + sentry: { + client: (nuxt) => ({ + integrations: [ + new BrowserTracing({ + routingInstrumentation: vueRouterInstrumentation(nuxt.$router, { + routeLabel: 'path', + }), + }), + new Replay(), + ], + tracesSampleRate: 1, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1, + }), + }, +}) diff --git a/src/runtime/plugin.ts b/src/runtime/plugin.ts deleted file mode 100644 index a54a02f..0000000 --- a/src/runtime/plugin.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { defineNuxtPlugin } from "#app"; - -export default defineNuxtPlugin((nuxtApp) => { - console.log("Plugin injected by @falcondev-it/nuxt-sentry!"); -}); diff --git a/src/runtime/sentry.client.ts b/src/runtime/sentry.client.ts new file mode 100644 index 0000000..59e4a88 --- /dev/null +++ b/src/runtime/sentry.client.ts @@ -0,0 +1,28 @@ +import { captureException, init, withScope } from '@sentry/vue' + +export default defineNuxtPlugin({ + enforce: 'pre', + setup(nuxt) { + if (!process.dev) return + + const dsn = useRuntimeConfig().public.sentry?.dsn + const { client } = useAppConfig().sentry + + init({ + app: nuxt.vueApp, + dsn, + ...client(nuxt.vueApp.$nuxt), + }) + + nuxt.vueApp.config.errorHandler = (err, context) => { + withScope((scope) => { + scope.setExtra('context', context) + captureException(err) + }) + } + + nuxt.hook('app:error', (err) => { + captureException(err) + }) + }, +})