feat: sentry integration and nice error element (#119)
* init Signed-off-by: Innei <i@innei.in> * sentry init Signed-off-by: Innei <i@innei.in> * update Signed-off-by: Innei <i@innei.in> * feat: sentry config Signed-off-by: Innei <i@innei.in> * feat: nice error element Signed-off-by: Innei <i@innei.in> --------- Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
ad26bd6f76
commit
2efb3a7086
|
|
@ -1,3 +1,5 @@
|
|||
VITE_WEB_URL=http://localhost:5173
|
||||
VITE_API_URL=http://localhost:3000
|
||||
VITE_IMGPROXY_URL=http://localhost:2873
|
||||
VITE_SENTRY_DSN=
|
||||
VITE_BUILD_TYPE=production
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ out
|
|||
.env
|
||||
.eslintcache
|
||||
.env.*
|
||||
|
||||
# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import fs from "node:fs"
|
||||
import { resolve } from "node:path"
|
||||
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import { defineConfig } from "electron-vite"
|
||||
|
||||
|
|
@ -23,10 +24,30 @@ export default defineConfig({
|
|||
"@pkg": resolve("./package.json"),
|
||||
},
|
||||
},
|
||||
plugins: [react()],
|
||||
plugins: [
|
||||
react(),
|
||||
sentryVitePlugin({
|
||||
org: "follow-rg",
|
||||
project: "follow",
|
||||
bundleSizeOptimizations: {
|
||||
excludeDebugStatements: true,
|
||||
// Only relevant if you added `browserTracingIntegration`
|
||||
excludePerformanceMonitoring: true,
|
||||
// Only relevant if you added `replayIntegration`
|
||||
excludeReplayIframe: true,
|
||||
excludeReplayShadowDom: true,
|
||||
excludeReplayWorker: true,
|
||||
},
|
||||
moduleMetadata: {
|
||||
appVersion:
|
||||
process.env.NODE_ENV === "development" ? "dev" : pkg.version,
|
||||
},
|
||||
}),
|
||||
],
|
||||
define: {
|
||||
APP_VERSION: JSON.stringify(pkg.version),
|
||||
APP_NAME: JSON.stringify(pkg.productName),
|
||||
APP_DEV_CWD: JSON.stringify(process.cwd()),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@
|
|||
"author": "example.com",
|
||||
"license": "UNLICENSED",
|
||||
"homepage": "https://electron-vite.org",
|
||||
"repository": {
|
||||
"url": "https://github.com/RSSNext/follow",
|
||||
"type": "git"
|
||||
},
|
||||
"main": "./dist/main/index.js",
|
||||
"scripts": {
|
||||
"build": "npm run typecheck && electron-vite build --outDir=dist && electron-forge package && electron-forge make",
|
||||
|
|
@ -48,6 +52,9 @@
|
|||
"@radix-ui/react-tabs": "1.1.0",
|
||||
"@radix-ui/react-toast": "1.2.1",
|
||||
"@radix-ui/react-tooltip": "1.1.2",
|
||||
"@sentry/electron": "5.1.0",
|
||||
"@sentry/react": "8.9.2",
|
||||
"@sentry/vite-plugin": "2.20.1",
|
||||
"@shikijs/transformers": "1.10.0",
|
||||
"@tanstack/query-sync-storage-persister": "5.49.0",
|
||||
"@tanstack/react-query": "5.49.0",
|
||||
|
|
|
|||
1123
pnpm-lock.yaml
1123
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,9 @@
|
|||
import path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url))
|
||||
const iconMap = {
|
||||
prod: path.join(__dirname, "../../resources/icon.png"),
|
||||
dev: path.join(__dirname, "../../resources/icon-dev.png"),
|
||||
}
|
||||
export const getIconPath = () => iconMap[process.env.NODE_ENV === "development" ? "dev" : "prod"]
|
||||
|
|
@ -1,35 +1,33 @@
|
|||
import path from "node:path"
|
||||
|
||||
import { registerIpcMain } from "@egoist/tipc/main"
|
||||
import * as Sentry from "@sentry/electron/main"
|
||||
import { app } from "electron"
|
||||
|
||||
import { getIconPath } from "./helper"
|
||||
import { registerAppMenu } from "./menu"
|
||||
import { router } from "./tipc"
|
||||
|
||||
const iconMap = {
|
||||
prod: path.join(__dirname, "../../resources/icon.png"),
|
||||
dev: path.join(__dirname, "../../resources/icon-dev.png"),
|
||||
}
|
||||
const appFolder = {
|
||||
prod: "Follow",
|
||||
dev: "Follow (dev)",
|
||||
}
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development"
|
||||
export const initializationApp = () => {
|
||||
Sentry.init({
|
||||
dsn: process.env.VITE_SENTRY_DSN,
|
||||
})
|
||||
|
||||
registerIpcMain(router)
|
||||
|
||||
app.setPath(
|
||||
"appData",
|
||||
path.join(
|
||||
app.getPath("appData"),
|
||||
isDev ? appFolder.dev : appFolder.prod,
|
||||
),
|
||||
path.join(app.getPath("appData"), isDev ? appFolder.dev : appFolder.prod),
|
||||
)
|
||||
|
||||
if (app.dock) {
|
||||
app.dock.setIcon(
|
||||
isDev ? iconMap.dev : iconMap.prod,
|
||||
)
|
||||
app.dock.setIcon(getIconPath())
|
||||
}
|
||||
|
||||
// In this file you can include the rest of your app"s specific main process
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
import { is } from "@electron-toolkit/utils"
|
||||
import { callGlobalContextMethod } from "@shared/bridge"
|
||||
import type { BrowserWindowConstructorOptions } from "electron"
|
||||
import { BrowserWindow, shell } from "electron"
|
||||
|
||||
import icon from "../../resources/icon.png?asset"
|
||||
import { getIconPath } from "./helper"
|
||||
import { store } from "./store"
|
||||
|
||||
const windows = {
|
||||
|
|
@ -14,7 +15,7 @@ const windows = {
|
|||
}
|
||||
|
||||
const { platform } = process
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url))
|
||||
export function createWindow(
|
||||
options: {
|
||||
extraPath?: string
|
||||
|
|
@ -30,7 +31,7 @@ export function createWindow(
|
|||
show: false,
|
||||
resizable: configs?.resizable ?? true,
|
||||
autoHideMenuBar: true,
|
||||
...(platform === "linux" ? { icon } : {}),
|
||||
...(platform === "linux" ? { icon: getIconPath() } : {}),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "../preload/index.mjs"),
|
||||
sandbox: false,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ function App() {
|
|||
document.removeEventListener("keydown", handleOpenSettings)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const windowsElectron = window.electron && getOS() === "Windows"
|
||||
return (
|
||||
<>
|
||||
|
|
@ -53,9 +54,7 @@ function App() {
|
|||
<div className="drag-region fixed top-0 z-[99999] flex h-[24px] w-full items-center justify-end rounded-t-[12px] bg-background">
|
||||
<div className="absolute left-5 top-0 flex h-[24px] items-center gap-2">
|
||||
<Logo className="size-4" />
|
||||
<span className="text-sm font-bold">
|
||||
{APP_NAME}
|
||||
</span>
|
||||
<span className="text-sm font-bold">{APP_NAME}</span>
|
||||
</div>
|
||||
<button
|
||||
className="no-drag-region flex h-[24px] w-[32px] items-center justify-center rounded duration-200 hover:bg-theme-item-active"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import type { FallbackRender } from "@sentry/react"
|
||||
import { ErrorBoundary } from "@sentry/react"
|
||||
import type { FC, PropsWithChildren } from "react"
|
||||
import { useCallback } from "react"
|
||||
|
||||
export interface AppErrorBoundaryProps extends PropsWithChildren {
|
||||
height?: number | string
|
||||
}
|
||||
|
||||
export const AppErrorBoundary: FC<AppErrorBoundaryProps> = (props) => {
|
||||
const fallbackRender: FallbackRender = useCallback(
|
||||
(fallbackProps) => (
|
||||
<ErrorFallback {...fallbackProps} height={props.height} />
|
||||
),
|
||||
[props.height],
|
||||
)
|
||||
|
||||
const onError = useCallback((error: unknown, componentStack?: string) => {
|
||||
console.error("Uncaught error:", error, componentStack)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ErrorBoundary fallback={fallbackRender} onError={onError}>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
export type AppErrorFallbackProps = Parameters<FallbackRender>[0] & {
|
||||
height?: number | string
|
||||
}
|
||||
|
||||
const ErrorFallback = (_props: AppErrorFallbackProps) => <div>App has crashed</div>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
import { repository } from "@pkg"
|
||||
import { clearLocalPersistStoreData } from "@renderer/store/utils/clear"
|
||||
import { useEffect } from "react"
|
||||
import { isRouteErrorResponse, useRouteError } from "react-router-dom"
|
||||
|
||||
import { StyledButton } from "../ui/button"
|
||||
|
||||
export const ErrorElement = DefaultErrorComponent
|
||||
|
||||
function DefaultErrorComponent() {
|
||||
const error = useRouteError()
|
||||
const message = isRouteErrorResponse(error) ?
|
||||
`${error.status} ${error.statusText}` :
|
||||
error instanceof Error ?
|
||||
error.message :
|
||||
JSON.stringify(error)
|
||||
const stack = error instanceof Error ? error.stack : null
|
||||
|
||||
useEffect(() => {
|
||||
console.error(
|
||||
"Error handled by React Router default ErrorBoundary:",
|
||||
error,
|
||||
)
|
||||
import("@sentry/react").then(({ captureException }) => {
|
||||
captureException(error)
|
||||
})
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div className="select-text p-8">
|
||||
<div className="drag-region fixed inset-x-0 top-0 h-12" />
|
||||
|
||||
<h2 className="mt-12 text-2xl">Unexpected Application Error!</h2>
|
||||
<h3 className="text-xl">{message}</h3>
|
||||
{import.meta.env.DEV && stack ? (
|
||||
<div className="mt-4 cursor-text overflow-auto whitespace-pre rounded-md bg-red-50 p-4 font-mono text-sm text-red-600">
|
||||
{attachOpenInEditor(stack)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<p className="my-8">
|
||||
The App has a temporary problem, click the button below to try reloading
|
||||
the app or another solution?
|
||||
</p>
|
||||
|
||||
<div className="center gap-4">
|
||||
<StyledButton
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
clearLocalPersistStoreData()
|
||||
window.location.href = "/"
|
||||
}}
|
||||
>
|
||||
Reset Local Database
|
||||
</StyledButton>
|
||||
<StyledButton onClick={() => (window.location.href = "/")}>
|
||||
Reload
|
||||
</StyledButton>
|
||||
</div>
|
||||
|
||||
<p className="mt-8">
|
||||
Still having this issue? Please give feedback in Github, thanks!
|
||||
<a
|
||||
className="ml-2 cursor-pointer text-theme-accent-400/80 duration-200 hover:text-theme-accent"
|
||||
href={`${repository.url}/issues/new?title=${encodeURIComponent(
|
||||
`Error: ${message}`,
|
||||
)}&body=${encodeURIComponent(
|
||||
`### Error\n\n${message}\n\n### Stack\n\n\`\`\`\n${stack}\n\`\`\``,
|
||||
)}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Submit Issue
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const attachOpenInEditor = (stack: string) => {
|
||||
const lines = stack.split("\n")
|
||||
return lines.map((line) => {
|
||||
// A line like this: at App (http://localhost:5173/src/App.tsx?t=1720527056591:41:9)
|
||||
// Find the `localhost` part and open the file in the editor
|
||||
if (!line.includes("at ")) {
|
||||
return line
|
||||
}
|
||||
const match = line.match(/(http:\/\/localhost:\d+\/[^:]+):(\d+):(\d+)/)
|
||||
|
||||
if (match) {
|
||||
const [o] = match
|
||||
|
||||
// Find `@fs/`
|
||||
// Like: `http://localhost:5173/@fs/Users/innei/git/work/rss3/follow/node_modules/.vite/deps/chunk-RPCDYKBN.js?v=757920f2:11548:26`
|
||||
const realFsPath = o.split("@fs")[1]
|
||||
|
||||
if (realFsPath) {
|
||||
return (
|
||||
// Delete `v=` hash, like `v=757920f2`
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
key={line}
|
||||
onClick={openInEditor.bind(
|
||||
null,
|
||||
realFsPath.replace(/\?v=[a-f0-9]+/, ""),
|
||||
)}
|
||||
>
|
||||
{line}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
// at App (http://localhost:5173/src/App.tsx?t=1720527056591:41:9)
|
||||
const srcFsPath = o.split("/src")[1]
|
||||
|
||||
if (srcFsPath) {
|
||||
const fs = srcFsPath.replace(/\?t=[a-f0-9]+/, "")
|
||||
|
||||
return (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
key={line}
|
||||
onClick={openInEditor.bind(
|
||||
null,
|
||||
`${APP_DEV_CWD}/src/renderer/src${fs}`,
|
||||
)}
|
||||
>
|
||||
{line}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return line
|
||||
})
|
||||
}
|
||||
// http://localhost:5173/src/App.tsx?t=1720527056591:41:9
|
||||
const openInEditor = (file: string) => {
|
||||
fetch(`/__open-in-editor?file=${encodeURIComponent(`${file}`)}`)
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import type { BrowserOptions } from "@sentry/react"
|
||||
import { FetchError } from "ofetch"
|
||||
|
||||
export const SentryConfig: BrowserOptions = {
|
||||
// Performance Monitoring
|
||||
tracesSampleRate: 1, // Capture 100% of the transactions
|
||||
// Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
|
||||
tracePropagationTargets: ["localhost", import.meta.env.VITE_API_URL],
|
||||
// Session Replay
|
||||
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||
replaysOnErrorSampleRate: 1,
|
||||
|
||||
beforeSend(event, hint) {
|
||||
const error = hint.originalException
|
||||
|
||||
if (
|
||||
error instanceof Error &&
|
||||
(/Network Error/i.test(error.message) ||
|
||||
/Fetch Error/i.test(error.message) ||
|
||||
/XHR Error/i.test(error.message) ||
|
||||
/adsbygoogle/i.test(error.message) ||
|
||||
/Failed to fetch/i.test(error.message))
|
||||
) {
|
||||
return null
|
||||
}
|
||||
if (error instanceof FetchError) {
|
||||
return null
|
||||
}
|
||||
return event
|
||||
},
|
||||
}
|
||||
|
|
@ -13,6 +13,12 @@ declare global {
|
|||
export type Id = string
|
||||
export type FeedId = Id
|
||||
export type EntryId = Id
|
||||
|
||||
export const SENTRY_RELEASE: { id: string }
|
||||
export const APP_DEV_CWD: string
|
||||
export interface Window {
|
||||
SENTRY_RELEASE: typeof SENTRY_RELEASE
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
import { authConfigManager } from "@hono/auth-js/react"
|
||||
import { version } from "@pkg"
|
||||
import { browserDB } from "@renderer/database"
|
||||
import { registerGlobalContext } from "@shared/bridge"
|
||||
import { enableMapSet } from "immer"
|
||||
import { useEffect } from "react"
|
||||
import {
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
} from "react-router-dom"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { subscribeNetworkStatus } from "./atoms/network"
|
||||
|
|
@ -10,6 +18,7 @@ import {
|
|||
getGeneralSettings,
|
||||
subscribeShouldUseIndexedDB,
|
||||
} from "./atoms/settings/general"
|
||||
import { SentryConfig } from "./configs"
|
||||
import { APP_NAME } from "./lib/constants"
|
||||
import { appLog } from "./lib/log"
|
||||
import { hydrateDatabaseToStore, setHydrated } from "./store/utils/hydrate"
|
||||
|
|
@ -24,6 +33,33 @@ const cleanup = subscribeShouldUseIndexedDB((value) => {
|
|||
}
|
||||
setHydrated(true)
|
||||
})
|
||||
|
||||
const initSentry = async () => {
|
||||
if (!window.SENTRY_RELEASE) return
|
||||
const Sentry = await import("@sentry/react")
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
environment: import.meta.env.VITE_BUILD_TYPE ?? "development",
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.replayIntegration(),
|
||||
Sentry.moduleMetadataIntegration(),
|
||||
Sentry.reactRouterV6BrowserTracingIntegration({
|
||||
useEffect,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
}),
|
||||
],
|
||||
...SentryConfig,
|
||||
})
|
||||
|
||||
Sentry.setTags({
|
||||
appVersion: version,
|
||||
})
|
||||
}
|
||||
|
||||
export const initializeApp = async () => {
|
||||
appLog(
|
||||
`${APP_NAME}: Next generation information browser`,
|
||||
|
|
@ -32,6 +68,7 @@ export const initializeApp = async () => {
|
|||
appLog(`Initialize ${APP_NAME}...`)
|
||||
|
||||
const now = Date.now()
|
||||
initSentry()
|
||||
subscribeNetworkStatus()
|
||||
|
||||
registerGlobalContext({
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import ReactDOM from "react-dom/client"
|
|||
import { RouterProvider } from "react-router-dom"
|
||||
|
||||
import { setAppIsReady } from "./atoms/app"
|
||||
import { AppErrorBoundary } from "./components/common/AppErrorBoundary"
|
||||
import { initializeApp } from "./init"
|
||||
import { getOS } from "./lib/utils"
|
||||
import { router } from "./router"
|
||||
|
|
@ -23,7 +24,9 @@ if (window.electron && getOS() === "Windows") {
|
|||
}
|
||||
ReactDOM.createRoot($container).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
<AppErrorBoundary>
|
||||
<RouterProvider router={router} />
|
||||
</AppErrorBoundary>
|
||||
<ClickToComponent />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -62,15 +62,19 @@ export function EntryColumn() {
|
|||
const entries = useEntriesByView()
|
||||
const { entriesIds, isFetchingNextPage } = entries
|
||||
|
||||
const { entryId: activeEntryId, view, feedId: routeFeedId, isPendingEntry, isCollection } = useRouteParms()
|
||||
const {
|
||||
entryId: activeEntryId,
|
||||
view,
|
||||
feedId: routeFeedId,
|
||||
isPendingEntry,
|
||||
isCollection,
|
||||
} = useRouteParms()
|
||||
const activeEntry = useEntry(activeEntryId)
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeEntryId) return
|
||||
|
||||
if (
|
||||
isCollection || isPendingEntry
|
||||
) return
|
||||
if (isCollection || isPendingEntry) return
|
||||
|
||||
const feedId = activeEntry?.feeds.id
|
||||
if (!feedId) return
|
||||
|
|
@ -167,10 +171,7 @@ const ListHeader: FC<{
|
|||
},
|
||||
})
|
||||
|
||||
if (
|
||||
typeof routerParams.feedId === "number" ||
|
||||
routerParams.isAllFeeds
|
||||
) {
|
||||
if (typeof routerParams.feedId === "number" || routerParams.isAllFeeds) {
|
||||
subscriptionActions.markReadByView(routerParams.view)
|
||||
} else if (
|
||||
routerParams.level === levels.folder &&
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ export function FeedColumn() {
|
|||
className="flex items-center gap-1 text-xl font-bold"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
navigateBackHome()
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
import { wrapCreateBrowserRouter } from "@sentry/react"
|
||||
import { createBrowserRouter, createHashRouter } from "react-router-dom"
|
||||
|
||||
import App from "./App"
|
||||
import { NotFound } from "./components/ui/not-found"
|
||||
import { ErrorElement } from "./components/common/ErrorElement"
|
||||
import { NotFound } from "./components/common/NotFound"
|
||||
import { buildGlobRoutes } from "./lib/route-builder"
|
||||
|
||||
const globTree = import.meta.glob("./pages/**/*.tsx")
|
||||
const tree = buildGlobRoutes(globTree)
|
||||
|
||||
const routerCreator = window.electron ? createHashRouter : createBrowserRouter
|
||||
let routerCreator = window.electron ? createHashRouter : createBrowserRouter
|
||||
if (window.SENTRY_RELEASE) {
|
||||
routerCreator = wrapCreateBrowserRouter(createHashRouter)
|
||||
}
|
||||
|
||||
export const router = routerCreator([
|
||||
{
|
||||
path: "/",
|
||||
element: <App />,
|
||||
|
||||
children: tree,
|
||||
errorElement: <ErrorElement />,
|
||||
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ interface ImportMetaEnv {
|
|||
VITE_WEB_URL: string
|
||||
VITE_API_URL: string
|
||||
VITE_IMGPROXY_URL: string
|
||||
VITE_SENTRY_DSN: string
|
||||
VITE_BUILD_TYPE: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { readFileSync } from "node:fs"
|
|||
import { resolve } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import { defineConfig } from "vite"
|
||||
|
||||
|
|
@ -11,6 +12,7 @@ export default defineConfig({
|
|||
build: {
|
||||
outDir: resolve(__dirname, "out/web"),
|
||||
target: "ES2022",
|
||||
sourcemap: true,
|
||||
},
|
||||
root: "./src/renderer",
|
||||
resolve: {
|
||||
|
|
@ -21,9 +23,22 @@ export default defineConfig({
|
|||
},
|
||||
},
|
||||
base: "/",
|
||||
plugins: [react()],
|
||||
plugins: [
|
||||
react(),
|
||||
sentryVitePlugin({
|
||||
org: "follow-rg",
|
||||
project: "follow",
|
||||
|
||||
moduleMetadata: {
|
||||
appVersion:
|
||||
process.env.NODE_ENV === "development" ? "dev" : pkg.version,
|
||||
electron: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
define: {
|
||||
APP_VERSION: JSON.stringify(pkg.version),
|
||||
APP_NAME: JSON.stringify(pkg.productName),
|
||||
APP_DEV_CWD: JSON.stringify(process.cwd()),
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue