From 320dd2f6d7b5b43b772778da9d61a52cbc799047 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 22 Feb 2026 16:22:47 +0000 Subject: [PATCH 1/4] Fail build on Sentry sourcemap upload errors Co-authored-by: Kent C. Dodds --- vite.config.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 040b8b91e..5a3df5de1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,6 @@ import 'dotenv/config' import { reactRouter } from '@react-router/dev/vite' import { sentryVitePlugin } from '@sentry/vite-plugin' import tailwindcss from '@tailwindcss/vite' -import { glob } from 'glob' import { defineConfig } from 'vite' import { envOnlyMacros } from 'vite-env-only' import { cjsInterop } from 'vite-plugin-cjs-interop' @@ -30,6 +29,12 @@ export default defineConfig(async () => { authToken: process.env.SENTRY_AUTH_TOKEN, org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, + // By default the bundler plugin logs and continues on upload/release + // errors. If we then also delete maps, Sentry ends up trying to fetch + // `*.map` and receiving our HTML 404 page instead. + errorHandler: (err) => { + throw err + }, release: { name: process.env.COMMIT_SHA, setCommits: { @@ -37,10 +42,13 @@ export default defineConfig(async () => { }, }, sourcemaps: { - filesToDeleteAfterUpload: await glob([ - './build/**/*.map', - '.server-build/**/*.map', - ]), + // NOTE: This option expects globs (or a Promise resolving to globs), + // not the *result* of running glob at config-eval time. + // + // We only delete Vite/React Router client maps here. The `server-build` + // sourcemaps are produced by a separate esbuild step and are not uploaded + // by this plugin. + filesToDeleteAfterUpload: ['./build/client/**/*.map'], }, }) : null, From fb55bf76e25666f51b72d668641013e1e283aa54 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 22 Feb 2026 16:26:13 +0000 Subject: [PATCH 2/4] Guard Sentry upload env and avoid silent sourcemap deletion Co-authored-by: Kent C. Dodds --- vite.config.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index 5a3df5de1..0a667774d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,6 +8,28 @@ import { cjsInterop } from 'vite-plugin-cjs-interop' import tsconfigPaths from 'vite-tsconfig-paths' const MODE = process.env.NODE_ENV +const SENTRY_UPLOAD = + process.env.SENTRY_UPLOAD === 'true' || process.env.SENTRY_UPLOAD === '1' + +if (SENTRY_UPLOAD && MODE === 'production') { + const authToken = process.env.SENTRY_AUTH_TOKEN + const project = process.env.SENTRY_PROJECT + const org = process.env.SENTRY_ORG + const tokenImpliesOrg = Boolean(authToken?.startsWith('sntrys_')) + + // If upload is "on" but required settings are missing, the Sentry plugin will + // skip upload but still run the delete-after-upload step, which results in + // `.map` URLs returning our HTML 404 page. + if (!authToken) { + throw new Error('SENTRY_UPLOAD is enabled, but SENTRY_AUTH_TOKEN is missing') + } + if (!project) { + throw new Error('SENTRY_UPLOAD is enabled, but SENTRY_PROJECT is missing') + } + if (!org && !tokenImpliesOrg) { + throw new Error('SENTRY_UPLOAD is enabled, but SENTRY_ORG is missing') + } +} export default defineConfig(async () => { return { @@ -23,7 +45,7 @@ export default defineConfig(async () => { tailwindcss(), reactRouter(), tsconfigPaths(), - process.env.SENTRY_UPLOAD + SENTRY_UPLOAD ? sentryVitePlugin({ disable: MODE !== 'production', authToken: process.env.SENTRY_AUTH_TOKEN, From 3d178e83a57a7eb59968eadd8917b85d9bf8b6ce Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 22 Feb 2026 16:36:05 +0000 Subject: [PATCH 3/4] Keep sourcemaps in build output Co-authored-by: Kent C. Dodds --- vite.config.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 0a667774d..1a5fdc764 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -18,8 +18,8 @@ if (SENTRY_UPLOAD && MODE === 'production') { const tokenImpliesOrg = Boolean(authToken?.startsWith('sntrys_')) // If upload is "on" but required settings are missing, the Sentry plugin will - // skip upload but still run the delete-after-upload step, which results in - // `.map` URLs returning our HTML 404 page. + // just warn + skip the upload. Fail fast so we don't silently deploy without + // sourcemaps in Sentry. if (!authToken) { throw new Error('SENTRY_UPLOAD is enabled, but SENTRY_AUTH_TOKEN is missing') } @@ -52,8 +52,7 @@ export default defineConfig(async () => { org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, // By default the bundler plugin logs and continues on upload/release - // errors. If we then also delete maps, Sentry ends up trying to fetch - // `*.map` and receiving our HTML 404 page instead. + // errors. Fail the build so we don't deploy with broken symbolication. errorHandler: (err) => { throw err }, @@ -63,15 +62,6 @@ export default defineConfig(async () => { auto: true, }, }, - sourcemaps: { - // NOTE: This option expects globs (or a Promise resolving to globs), - // not the *result* of running glob at config-eval time. - // - // We only delete Vite/React Router client maps here. The `server-build` - // sourcemaps are produced by a separate esbuild step and are not uploaded - // by this plugin. - filesToDeleteAfterUpload: ['./build/client/**/*.map'], - }, }) : null, ], From e806864fbd875d422d165726b68e17a2cf8c858f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 22 Feb 2026 20:27:01 +0000 Subject: [PATCH 4/4] Stabilize CI mocks Sentry init Co-authored-by: Kent C. Dodds --- server/index.ts | 10 ++++++++-- vite.config.ts | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/server/index.ts b/server/index.ts index 5caed72cb..874cd57f1 100644 --- a/server/index.ts +++ b/server/index.ts @@ -87,11 +87,17 @@ const getHost = (req: { get: (key: string) => string | undefined }) => const MODE = process.env.NODE_ENV -if (MODE === 'production' && process.env.SENTRY_DSN) { +const SHOULD_INIT_SENTRY = + MODE === 'production' && + Boolean(process.env.SENTRY_DSN) && + // `start:mocks` (used in CI + local e2e) runs with `MOCKS=true`. + process.env.MOCKS !== 'true' + +if (SHOULD_INIT_SENTRY) { void import('./utils/monitoring.js').then(({ init }) => init()) } -if (MODE === 'production') { +if (SHOULD_INIT_SENTRY) { sentryInit({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 0.3, diff --git a/vite.config.ts b/vite.config.ts index 1a5fdc764..e6ec0e741 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,6 +15,8 @@ if (SENTRY_UPLOAD && MODE === 'production') { const authToken = process.env.SENTRY_AUTH_TOKEN const project = process.env.SENTRY_PROJECT const org = process.env.SENTRY_ORG + // New-style Sentry auth tokens (prefix "sntrys_") embed the org, so SENTRY_ORG + // is not required when using one. const tokenImpliesOrg = Boolean(authToken?.startsWith('sntrys_')) // If upload is "on" but required settings are missing, the Sentry plugin will @@ -66,6 +68,10 @@ export default defineConfig(async () => { : null, ], build: { + // This is an OSS project, so it's fine to generate "regular" sourcemaps. + // If we ever want sourcemaps upload without exposing them publicly, switch + // to `sourcemap: 'hidden'` (and keep uploading to Sentry). + sourcemap: true, cssMinify: MODE === 'production', rollupOptions: { external: [/node:.*/, 'stream', 'crypto'],