diff --git a/docs/pages/configuration/sentry.mdx b/docs/pages/configuration/sentry.mdx new file mode 100644 index 00000000..5c125dfa --- /dev/null +++ b/docs/pages/configuration/sentry.mdx @@ -0,0 +1,41 @@ +--- +sidebar_icon: bug +--- + +# Sentry + +Sentry is a popular error tracking tool that helps you monitor and fix crashes in real time. It provides you with detailed error reports, so you can quickly identify and resolve issues before they affect your users. + +## Enable Sentry + +1. To enable it you have to first install the optional dependency `@sentry/react`. + +```bash +npm install --save @sentry/react +``` + +2. And then set the `SENTRY_DSN` environment variable in your Zudoku project. + +```bash + +SENTRY_DSN=https://your-sentry-dsn +``` + +## Release management + +However this does not handle release management for you. For that you can [create a custom `vite.config.ts`](./vite-config) and use the `@sentry/vite-plugin` plugin. + +```ts +import { sentryVitePlugin } from "@sentry/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + sentryVitePlugin({ + authToken: "your-token", + org: "your-org", + project: "your-project", + }), + ], +}); +``` diff --git a/docs/sidebar.ts b/docs/sidebar.ts deleted file mode 100644 index ffe81a1d..00000000 --- a/docs/sidebar.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Sidebar } from "zudoku"; - -export const sidebar: Sidebar = { - docs: [ - { - type: "category", - label: "Getting started", - icon: "sparkles", - items: ["introduction", "app-quickstart", "html-quickstart"], - }, - { - type: "category", - label: "Configuration", - icon: "cog", - link: "configuration/overview", - items: [ - "configuration/api-reference", - "configuration/navigation", - "configuration/search", - "configuration/authentication", - "configuration/vite-config", - ], - }, - { - type: "category", - label: "Markdown", - icon: "book-open-text", - link: "markdown/overview", - items: ["markdown/mdx", "markdown/admonitions", "markdown/code-blocks"], - }, - { - type: "category", - label: "Guide", - icon: "monitor-check", - items: ["environment-variables", "custom-pages", "using-multiple-apis"], - }, - { - type: "category", - label: "Deployment", - icon: "cloud-upload", - link: "deployment", - items: [ - "deploy/cloudflare-pages", - "deploy/github-pages", - "deploy/vercel", - "deploy/direct-upload", - ], - }, - { - type: "category", - label: "Extending", - icon: "blocks", - items: ["custom-plugins", "api-keys"], - }, - ], -}; diff --git a/docs/zudoku.config.tsx b/docs/zudoku.config.tsx index c0442fc8..6ca3faa3 100644 --- a/docs/zudoku.config.tsx +++ b/docs/zudoku.config.tsx @@ -50,6 +50,7 @@ const config: ZudokuConfig = { "configuration/navigation", "configuration/search", "configuration/authentication", + "configuration/sentry", "configuration/vite-config", ], }, diff --git a/packages/zudoku/package.json b/packages/zudoku/package.json index 956333cc..47c6e00f 100644 --- a/packages/zudoku/package.json +++ b/packages/zudoku/package.json @@ -280,6 +280,7 @@ }, "optionalDependencies": { "@clerk/clerk-js": "5.11.0", - "@inkeep/widgets": "^0.2.289" + "@inkeep/widgets": "^0.2.289", + "@sentry/react": "^8.45.1" } } diff --git a/packages/zudoku/src/app/entry.client.tsx b/packages/zudoku/src/app/entry.client.tsx index cb469a09..25265b34 100644 --- a/packages/zudoku/src/app/entry.client.tsx +++ b/packages/zudoku/src/app/entry.client.tsx @@ -14,6 +14,12 @@ import { getRoutesByConfig } from "./main.js"; const routes = getRoutesByConfig(config); const root = document.getElementById("root")!; +if (process.env.SENTRY_DSN) { + void import("./sentry.js").then((mod) => { + mod.initSentry({ dsn: process.env.SENTRY_DSN as string }); + }); +} + if (root.childElementCount > 0) { void hydrate(routes); } else { diff --git a/packages/zudoku/src/app/sentry.ts b/packages/zudoku/src/app/sentry.ts new file mode 100644 index 00000000..f440eaab --- /dev/null +++ b/packages/zudoku/src/app/sentry.ts @@ -0,0 +1,24 @@ +import * as Sentry from "@sentry/react"; +import { useEffect } from "react"; +import { + createRoutesFromChildren, + matchRoutes, + useLocation, + useNavigationType, +} from "react-router-dom"; + +export const initSentry = ({ dsn }: { dsn: string }) => { + Sentry.init({ + dsn, + integrations: [ + Sentry.reactRouterV6BrowserTracingIntegration({ + useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + Sentry.replayIntegration(), + ], + }); +}; diff --git a/packages/zudoku/src/vite/config.ts b/packages/zudoku/src/vite/config.ts index 0c669624..0ad8e440 100644 --- a/packages/zudoku/src/vite/config.ts +++ b/packages/zudoku/src/vite/config.ts @@ -171,6 +171,9 @@ export async function getViteConfig( worker: { format: "es", }, + define: { + "process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN), + }, ssr: { target: "node", noExternal: [], @@ -228,7 +231,7 @@ export async function getViteConfig( ? getAppServerEntryPath() : getAppClientEntryPath(), ], - include: ["react-dom/client"], + include: ["react-dom/client", "@sentry/react"], exclude: [ // Vite does not like optimizing the worker dependency "zudoku/openapi-worker", diff --git a/packages/zudoku/vite.config.ts b/packages/zudoku/vite.config.ts index 5df3dc4f..cee4845f 100644 --- a/packages/zudoku/vite.config.ts +++ b/packages/zudoku/vite.config.ts @@ -55,6 +55,7 @@ export default defineConfig({ "react-dom", "lucide-react", /@radix-ui/, + "@sentry/react", // Optional Modules (i.e. auth providers) are external as we don't // want to bundle these in the library. Users will install these diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac167d4b..1a9e9ccb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -652,6 +652,9 @@ importers: '@inkeep/widgets': specifier: ^0.2.289 version: 0.2.289(@internationalized/date@3.5.5)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@sentry/react': + specifier: ^8.45.1 + version: 8.45.1(react@18.3.1) devDependencies: '@graphql-codegen/cli': specifier: 5.0.3 @@ -3811,10 +3814,34 @@ packages: '@rushstack/eslint-patch@1.10.4': resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} + '@sentry-internal/browser-utils@8.45.1': + resolution: {integrity: sha512-sZwtP3zAzDsjUS7WkMW5VGbvSl7hGKTMc8gAJbpEsrybMxllIP13zzMRwpeFF11RnnvbrZ/FtAeX58Mvj0jahA==} + engines: {node: '>=14.18'} + + '@sentry-internal/feedback@8.45.1': + resolution: {integrity: sha512-zCKptzki4SLnG+s8je8dgnppOKFjiiO4GVBc4fh7uL8zjNPBnxW8wK4SrPfAEKVYaHUzkKc5vixwUqcpmfLLGw==} + engines: {node: '>=14.18'} + + '@sentry-internal/replay-canvas@8.45.1': + resolution: {integrity: sha512-qiPg6XwOwkiMMe/8Qf3EhXCqkSlSnWLlorYngIbdkV2klbWjd7vKnqkFJF4PnaS0g7kkZr7nh+MdzpyLyuj2Mw==} + engines: {node: '>=14.18'} + + '@sentry-internal/replay@8.45.1': + resolution: {integrity: sha512-cOA9CodNSR9+hmICDaGIDUvWiwxQxeMHk/esbjB8uAW8HG4CYTG3CTYTZmlmou7DuysfMd4JNuFmDFBj+YU5/A==} + engines: {node: '>=14.18'} + + '@sentry/browser@8.45.1': + resolution: {integrity: sha512-/KvYhQSRg8m9kotG8h9FrfXCWRlebrvdfXKjj1oE9SyZ2LmR8Ze9AcEw1qzsBsa1F1D/a5FQbUJahSoLBkaQPA==} + engines: {node: '>=14.18'} + '@sentry/core@8.34.0': resolution: {integrity: sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA==} engines: {node: '>=14.18'} + '@sentry/core@8.45.1': + resolution: {integrity: sha512-1fGmkr0paZshh38mD29c4CfkRkgFoYDaAGyDLoGYfTbEph/lU8RHB2HWzN93McqNdMEhl1DRRyqIasUZoPlqSA==} + engines: {node: '>=14.18'} + '@sentry/node@8.34.0': resolution: {integrity: sha512-Q7BPp7Y8yCcwD620xoziWSOuPi/PCIdttkczvB0BGzBRYh2s702h+qNusRijRpVNZmzmYOo9m1x7Y1O/b8/v2A==} engines: {node: '>=14.18'} @@ -3829,6 +3856,12 @@ packages: '@opentelemetry/sdk-trace-base': ^1.26.0 '@opentelemetry/semantic-conventions': ^1.27.0 + '@sentry/react@8.45.1': + resolution: {integrity: sha512-ooMR2Lt4YSc5CMJklBKiL/mb+uidcZMpflxUvVUbtWMif+PqTUkfPRyICv6vs7muxK9i84Rr4iCkyZ4ns4H27A==} + engines: {node: '>=14.18'} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + '@sentry/types@8.34.0': resolution: {integrity: sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ==} engines: {node: '>=14.18'} @@ -13333,11 +13366,45 @@ snapshots: '@rushstack/eslint-patch@1.10.4': {} + '@sentry-internal/browser-utils@8.45.1': + dependencies: + '@sentry/core': 8.45.1 + optional: true + + '@sentry-internal/feedback@8.45.1': + dependencies: + '@sentry/core': 8.45.1 + optional: true + + '@sentry-internal/replay-canvas@8.45.1': + dependencies: + '@sentry-internal/replay': 8.45.1 + '@sentry/core': 8.45.1 + optional: true + + '@sentry-internal/replay@8.45.1': + dependencies: + '@sentry-internal/browser-utils': 8.45.1 + '@sentry/core': 8.45.1 + optional: true + + '@sentry/browser@8.45.1': + dependencies: + '@sentry-internal/browser-utils': 8.45.1 + '@sentry-internal/feedback': 8.45.1 + '@sentry-internal/replay': 8.45.1 + '@sentry-internal/replay-canvas': 8.45.1 + '@sentry/core': 8.45.1 + optional: true + '@sentry/core@8.34.0': dependencies: '@sentry/types': 8.34.0 '@sentry/utils': 8.34.0 + '@sentry/core@8.45.1': + optional: true + '@sentry/node@8.34.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -13389,6 +13456,14 @@ snapshots: '@sentry/types': 8.34.0 '@sentry/utils': 8.34.0 + '@sentry/react@8.45.1(react@18.3.1)': + dependencies: + '@sentry/browser': 8.45.1 + '@sentry/core': 8.45.1 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optional: true + '@sentry/types@8.34.0': {} '@sentry/utils@8.34.0': @@ -16252,7 +16327,7 @@ snapshots: '@typescript-eslint/parser': 8.13.0(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.2(eslint@8.57.0) @@ -16275,12 +16350,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.1 get-tsconfig: 4.8.1 @@ -16292,14 +16367,14 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.13.0(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -16314,7 +16389,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3